Merge branch 'geoapi-4.0' into geoapi-3.1
diff --git a/.gitmodules b/.gitmodules
index e9a9e64..aab14f3 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,4 @@
 [submodule "geoapi/snapshot"]
 	path = geoapi/snapshot
 	url = https://github.com/opengeospatial/geoapi
+	branch = 3.1.x
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 ba1e3b9..8863453 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 = "4.0-SNAPSHOT";
+    private static final String GEOAPI_VERSION = "3.1-SNAPSHOT";
 
     /**
      * Attributes where values are class names. Those attributes can be assigned
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 ca0059b..05fe6dc 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
@@ -99,6 +99,8 @@
 Identification
 IdentifiedObject
 Identifier
+ImageCRS
+ImageDatum
 ImageDescription
 ImagingCondition
 Individual
@@ -119,6 +121,7 @@
 MathTransform2D
 Medium
 MediumFormat
+MediumName
 Metadata
 MetadataExtensionInformation
 MetadataScope
@@ -219,6 +222,7 @@
 UML
 Usability
 Usage
+UserDefinedCS
 ValidatorContainer
 Validators
 VectorSpatialRepresentation
diff --git a/buildSrc/src/main/resources/org/apache/sis/buildtools/book/OGC.lst b/buildSrc/src/main/resources/org/apache/sis/buildtools/book/OGC.lst
index f9449cb..fd087b2 100644
--- a/buildSrc/src/main/resources/org/apache/sis/buildtools/book/OGC.lst
+++ b/buildSrc/src/main/resources/org/apache/sis/buildtools/book/OGC.lst
@@ -17,6 +17,7 @@
 CD_Ellipsoid
 CD_EngineeringDatum
 CD_GeodeticDatum
+CD_ImageDatum
 CD_PixelInCell
 CD_PrimeMeridian
 CD_TemporalDatum
@@ -52,6 +53,7 @@
 CS_RangeMeaning
 CS_SphericalCS
 CS_TimeCS
+CS_UserDefinedCS
 CS_VerticalCS
 CV_ContinuousCoverage
 CV_Coverage
@@ -149,6 +151,7 @@
 MD_MaintenanceInformation
 MD_Medium
 MD_MediumFormatCode
+MD_MediumNameCode
 MD_Metadata
 MD_MetadataExtensionInformation
 MD_MetadataScope
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 9b11664..7d87734 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
@@ -79,12 +79,12 @@
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.logging.Logging;
 
+// 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 geoapi-4.0 branch:
-import org.opengis.coordinate.MismatchedDimensionException;
-
 
 /**
  * The "transform" subcommand.
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 5cf4dfb..3f7ed38 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
@@ -247,7 +247,7 @@
         if (bounds != null) {
             final int bd = bounds.getDimension();
             if (bd != dimension) {
-                throw new MismatchedDimensionException(Errors.format(
+                throw new org.opengis.geometry.MismatchedDimensionException(Errors.format(
                         Errors.Keys.MismatchedDimension_3, "bounds", dimension, bd));
             }
         }
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 e7c53d1..9d22ed1 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
@@ -571,7 +571,7 @@
             final int expected = gridGeometry.getDimension();
             final int dimension = sliceExtent.getDimension();
             if (expected != dimension) {
-                throw new MismatchedDimensionException(Errors.format(
+                throw new org.opengis.geometry.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/GridExtent.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridExtent.java
index 58e1a55..8a2805c 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
@@ -1974,7 +1974,7 @@
         final int n = coordinates.length;
         final int m = n >>> 1;
         if (n != other.coordinates.length) {
-            throw new MismatchedDimensionException(Errors.format(
+            throw new org.opengis.geometry.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/GridGeometry.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridGeometry.java
index 89880ba..d563bec 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
@@ -570,7 +570,7 @@
         if (extent != null) {
             final int dimension = extent.getDimension();
             if (dimension != expected) {
-                throw new MismatchedDimensionException(Errors.format(
+                throw new org.opengis.geometry.MismatchedDimensionException(Errors.format(
                         Errors.Keys.MismatchedDimension_3, "extent", expected, dimension));
             }
         }
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/ImageRenderer.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/ImageRenderer.java
index 569e9b3..bce137d 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/ImageRenderer.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/ImageRenderer.java
@@ -60,8 +60,8 @@
 import static org.apache.sis.image.PlanarImage.GRID_GEOMETRY_KEY;
 import static org.apache.sis.image.PlanarImage.SAMPLE_DIMENSIONS_KEY;
 
-// Specific to the geoapi-4.0 branch:
-import org.opengis.coordinate.MismatchedDimensionException;
+// Specific to the main and geoapi-3.1 branches:
+import org.opengis.geometry.MismatchedDimensionException;
 
 
 /**
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 d378c95..1d99dbb 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
@@ -318,8 +318,8 @@
      * @since 0.8
      */
     @Override
-    public Optional<InternationalString> getRemarks() {
-        return Optional.ofNullable(deprecated ? description : null);
+    public InternationalString getRemarks() {
+        return deprecated ? description : null;
     }
 
     /**
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 e9dcaeb..614f3ce 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
@@ -661,7 +661,7 @@
                     case REMARKS: {
                         if (org.apache.sis.feature.Field.isDeprecated(propertyType)) {
                             table.append(resources.getString(Vocabulary.Keys.Deprecated));
-                            final InternationalString r = ((Deprecable) propertyType).getRemarks().orElse(null);
+                            final InternationalString r = ((Deprecable) propertyType).getRemarks();
                             if (r != null) {
                                 remarks.add(r.toString(displayLocale));
                                 appendSuperscript(remarks.size(), table);
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 fcf8007..6413f86 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
@@ -31,6 +31,9 @@
 import org.apache.sis.referencing.NamedIdentifier;
 import org.apache.sis.util.resources.Errors;
 
+// 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;
@@ -41,9 +44,6 @@
 import org.opengis.feature.FeatureAssociation;
 import org.opengis.feature.FeatureAssociationRole;
 
-// Specific to the geoapi-4.0 branch:
-import org.apache.sis.metadata.iso.maintenance.DefaultScope;
-
 
 /**
  * Provides validation methods to be shared by different implementations.
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 f8a4d2b..a2289c7 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
@@ -37,8 +37,8 @@
 import org.apache.sis.setup.GeometryLibrary;
 import org.apache.sis.util.resources.Errors;
 
-// Specific to the geoapi-4.0 branch:
-import org.opengis.coordinate.MismatchedDimensionException;
+// Specific to the main and geoapi-3.1 branches:
+import org.opengis.geometry.MismatchedDimensionException;
 
 
 /**
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 252da84..39f0671 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.MathTransform;
 import org.opengis.referencing.operation.TransformException;
+import org.opengis.referencing.operation.MathTransform;
 import org.opengis.util.FactoryException;
 import org.apache.sis.geometry.GeneralEnvelope;
 import org.apache.sis.filter.sqlmm.SQLMM;
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 208b00e..0ef3b23 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
@@ -46,13 +46,13 @@
 import org.apache.sis.util.privy.Constants;
 import org.apache.sis.metadata.iso.citation.Citations;
 
+// 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 geoapi-4.0 branch:
-import org.opengis.referencing.crs.DerivedCRS;
-
 
 /**
  * Context (such as desired CRS) in which a spatial operator will be executed.
@@ -306,6 +306,7 @@
      * @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,
@@ -321,8 +322,8 @@
             if (Units.isLinear(systemUnit) && targetCRS instanceof GeographicCRS) {
                 return Projector.instance().create((GeographicCRS) targetCRS, geometry.getCentroid(), geometryCRS);
             }
-            if (targetCRS instanceof DerivedCRS) {
-                targetCRS = ((DerivedCRS) targetCRS).getBaseCRS();
+            if (targetCRS instanceof GeneralDerivedCRS) {
+                targetCRS = ((GeneralDerivedCRS) targetCRS).getBaseCRS();
             } else {
                 throw new IncommensurableException(Errors.format(Errors.Keys.InconsistentUnitsForCS_1, systemUnit));
             }
@@ -374,6 +375,7 @@
          * @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
         {
@@ -383,8 +385,8 @@
              * Note that a CRS can be both derived and geographic, so we need to do this check first in order to
              * avoid derived geographic CRS such as the ones having rotated poles.
              */
-            while (geometryCRS instanceof DerivedCRS) {
-                final var g = (DerivedCRS) geometryCRS;
+            while (geometryCRS instanceof GeneralDerivedCRS) {
+                final GeneralDerivedCRS g = (GeneralDerivedCRS) geometryCRS;
                 centroid = g.getConversionFromBase().getMathTransform().inverse().transform(centroid, centroid);
                 geometryCRS = g.getBaseCRS();
             }
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 eadbe51..c2650f4 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.MathTransform;
 import org.opengis.referencing.operation.TransformException;
+import org.opengis.referencing.operation.MathTransform;
 import org.apache.sis.referencing.privy.ReferencingUtilities;
 import org.apache.sis.geometry.DirectPosition2D;
 import org.apache.sis.geometry.GeneralDirectPosition;
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 4318272..b52d849 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
@@ -29,8 +29,8 @@
 import org.opengis.temporal.TemporalPrimitive;
 import org.opengis.temporal.TemporalGeometricPrimitive;
 
-// Specific to the geoapi-4.0 branch:
-import org.opengis.metadata.Identifier;
+// Specific to the geoapi-3.1 branch:
+import org.opengis.referencing.ReferenceIdentifier;
 
 
 /**
@@ -62,7 +62,7 @@
     @Override public Instant getEnding()    {return Instant.ofEpochMilli(end);}
 
     /** Not needed for the tests. */
-    @Override public Identifier       getName()                              {throw new UnsupportedOperationException();}
+    @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();}
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 f25dfcb..a28bc00 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
@@ -33,9 +33,9 @@
 import org.apache.sis.util.collection.CheckedContainer;
 import org.apache.sis.util.logging.Logging;
 
-// Specific to the geoapi-4.0 branch:
-import org.opengis.annotation.Obligation;
-import org.opengis.metadata.citation.Responsibility;
+// Specific to the main and geoapi-3.1 branches:
+import org.opengis.metadata.Obligation;
+import org.opengis.metadata.citation.ResponsibleParty;
 
 
 /**
@@ -305,7 +305,7 @@
      * Returns the name of the person or organization creating the element.
      */
     @Override
-    public Collection<? extends Responsibility> getSources() {
+    public Collection<? extends ResponsibleParty> getSources() {
         return authority.getCitedResponsibleParties();
     }
 
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 7b2c089..4be6930 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
@@ -35,13 +35,13 @@
 import org.apache.sis.util.privy.Strings;
 import org.apache.sis.util.logging.Logging;
 
+// 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;
 
-// Specific to the geoapi-4.0 branch:
-import org.opengis.metadata.citation.Responsibility;
-
 
 /**
  * Base class for the {@code public static final Citation} constants defined in some SIS classes.
@@ -217,10 +217,10 @@
     @Override public InternationalString                        getEdition()                 {return delegate().getEdition();}
     @Override public Date                                       getEditionDate()             {return delegate().getEditionDate();}
     @Override public Collection<? extends Identifier>           getIdentifiers()             {return delegate().getIdentifiers();}
-    @Override public Collection<? extends Responsibility>       getCitedResponsibleParties() {return delegate().getCitedResponsibleParties();}
+    @Override public Collection<? extends ResponsibleParty>     getCitedResponsibleParties() {return delegate().getCitedResponsibleParties();}
     @Override public Collection<PresentationForm>               getPresentationForms()       {return delegate().getPresentationForms();}
     @Override public Series                                     getSeries()                  {return delegate().getSeries();}
-    @Override public Collection<? extends InternationalString>  getOtherCitationDetails()    {return delegate().getOtherCitationDetails();}
+    @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();}
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/DefaultApplicationSchemaInformation.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/DefaultApplicationSchemaInformation.java
index ae2b2a4..be816d5 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/DefaultApplicationSchemaInformation.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/DefaultApplicationSchemaInformation.java
@@ -25,9 +25,8 @@
 import org.opengis.metadata.citation.OnlineResource;
 import org.apache.sis.xml.Namespaces;
 
-// Specific to the geoapi-4.0 branch:
-import org.apache.sis.xml.bind.gco.CharSequenceAdapter;
-import org.apache.sis.xml.bind.metadata.CI_OnlineResource;
+// Specific to the main and geoapi-3.1 branches:
+import java.net.URI;
 
 
 /**
@@ -71,7 +70,7 @@
     /**
      * Serial number for inter-operability with different versions.
      */
-    private static final long serialVersionUID = 5667352094985433121L;
+    private static final long serialVersionUID = -884081423040392985L;
 
     /**
      * Name of the application schema used.
@@ -93,19 +92,19 @@
      * Full application schema given as an ASCII file.
      */
     @SuppressWarnings("serial")
-    private CharSequence schemaAscii;
+    private URI schemaAscii;
 
     /**
      * Full application schema given as a graphics file.
      */
     @SuppressWarnings("serial")
-    private OnlineResource graphicsFile;
+    private URI graphicsFile;
 
     /**
      * Full application schema given as a software development file.
      */
     @SuppressWarnings("serial")
-    private OnlineResource softwareDevelopmentFile;
+    private URI softwareDevelopmentFile;
 
     /**
      * Software dependent format used for the application schema software dependent file.
@@ -247,21 +246,29 @@
     /**
      * Full application schema given as an ASCII file.
      *
+     * <div class="warning"><b>Upcoming API change</b><br>
+     * {@code URI} may be replaced by {@link CharSequence} in GeoAPI 4.0.
+     * </div>
+     *
      * @return application schema as an ASCII file, or {@code null}.
      */
     @Override
     @XmlElement(name = "schemaAscii")
-    @XmlJavaTypeAdapter(CharSequenceAdapter.Since2014.class)
-    public CharSequence getSchemaAscii()  {
+    @XmlJavaTypeAdapter(URIStringAdapter.class)
+    public URI getSchemaAscii()  {
         return schemaAscii;
     }
 
     /**
      * Sets the full application schema given as an ASCII file.
      *
+     * <div class="warning"><b>Upcoming API change</b><br>
+     * {@code URI} may be replaced by {@link CharSequence} in GeoAPI 4.0.
+     * </div>
+     *
      * @param  newValue  the new ASCII file.
      */
-    public void setSchemaAscii(final CharSequence newValue) {
+    public void setSchemaAscii(final URI newValue) {
         checkWritePermission(schemaAscii);
         schemaAscii = newValue;
     }
@@ -269,21 +276,31 @@
     /**
      * Full application schema given as a graphics file.
      *
+     * <div class="warning"><b>Upcoming API change</b><br>
+     * As of ISO 19115:2014, {@code URI} is replaced by {@link OnlineResource}.
+     * This change may be applied in GeoAPI 4.0.
+     * </div>
+     *
      * @return application schema as a graphics file, or {@code null}.
      */
     @Override
     @XmlElement(name = "graphicsFile")
-    @XmlJavaTypeAdapter(CI_OnlineResource.Since2014.class)
-    public OnlineResource getGraphicsFile()  {
+    @XmlJavaTypeAdapter(OnlineResourceAdapter.class)
+    public URI getGraphicsFile()  {
         return graphicsFile;
     }
 
     /**
      * Sets the full application schema given as a graphics file.
      *
+     * <div class="warning"><b>Upcoming API change</b><br>
+     * As of ISO 19115:2014, {@code URI} is replaced by {@link OnlineResource}.
+     * This change may be applied in GeoAPI 4.0.
+     * </div>
+     *
      * @param  newValue  the new graphics file.
      */
-    public void setGraphicsFile(final OnlineResource newValue) {
+    public void setGraphicsFile(final URI newValue) {
         checkWritePermission(graphicsFile);
         graphicsFile = newValue;
     }
@@ -291,21 +308,31 @@
     /**
      * Full application schema given as a software development file.
      *
+     * <div class="warning"><b>Upcoming API change</b><br>
+     * As of ISO 19115:2014, {@code URI} is replaced by {@link OnlineResource}.
+     * This change may be applied in GeoAPI 4.0.
+     * </div>
+     *
      * @return application schema as a software development file, or {@code null}.
      */
     @Override
     @XmlElement(name = "softwareDevelopmentFile")
-    @XmlJavaTypeAdapter(CI_OnlineResource.Since2014.class)
-    public OnlineResource getSoftwareDevelopmentFile()  {
+    @XmlJavaTypeAdapter(OnlineResourceAdapter.class)
+    public URI getSoftwareDevelopmentFile()  {
         return softwareDevelopmentFile;
     }
 
     /**
      * Sets the full application schema given as a software development file.
      *
+     * <div class="warning"><b>Upcoming API change</b><br>
+     * As of ISO 19115:2014, {@code URI} is replaced by {@link OnlineResource}.
+     * This change may be applied in GeoAPI 4.0.
+     * </div>
+     *
      * @param  newValue  the new software development file.
      */
-    public void setSoftwareDevelopmentFile(final OnlineResource newValue) {
+    public void setSoftwareDevelopmentFile(final URI newValue) {
         checkWritePermission(softwareDevelopmentFile);
         softwareDevelopmentFile = newValue;
     }
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 9e58d0d..1ed8ffc 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
@@ -36,14 +36,14 @@
 import org.apache.sis.util.privy.CollectionsExt;
 import static org.apache.sis.metadata.privy.ImplementationHelper.ensurePositive;
 
+// Specific to the main and geoapi-3.1 branches:
+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 geoapi-4.0 branch:
-import org.opengis.annotation.Obligation;
-import org.opengis.metadata.citation.Responsibility;
-
 
 /**
  * New metadata element, not found in ISO 19115, which is required to describe geographic data.
@@ -103,7 +103,7 @@
     /**
      * Serial number for inter-operability with different versions.
      */
-    private static final long serialVersionUID = 489138542195499530L;
+    private static final long serialVersionUID = 5892811836634834434L;
 
     /**
      * Name of the extended metadata element.
@@ -184,13 +184,13 @@
      * Reason for creating the extended element.
      */
     @SuppressWarnings("serial")
-    private InternationalString rationale;
+    private Collection<InternationalString> rationales;
 
     /**
      * Name of the person or organization creating the extended element.
      */
     @SuppressWarnings("serial")
-    private Collection<Responsibility> sources;
+    private Collection<ResponsibleParty> sources;
 
     /**
      * Construct an initially empty extended element information.
@@ -215,7 +215,7 @@
                                              final Datatype     dataType,
                                              final String       parentEntity,
                                              final CharSequence rule,
-                                             final Responsibility source)
+                                             final ResponsibleParty source)
     {
         this.name         = name;
         this.definition   = Types.toInternationalString(definition);
@@ -223,7 +223,7 @@
         this.dataType     = dataType;
         this.parentEntity = singleton(parentEntity, String.class);
         this.rule         = Types.toInternationalString(rule);
-        this.sources      = singleton(source, Responsibility.class);
+        this.sources      = singleton(source, ResponsibleParty.class);
     }
 
     /**
@@ -256,8 +256,8 @@
             domainValue       = object.getDomainValue();
             parentEntity      = copyCollection(object.getParentEntity(), String.class);
             rule              = object.getRule();
-            rationale         = object.getRationale();
-            sources           = copyCollection(object.getSources(), Responsibility.class);
+            rationales        = copyCollection(object.getRationales(), InternationalString.class);
+            sources           = copyCollection(object.getSources(), ResponsibleParty.class);
         }
     }
 
@@ -554,7 +554,8 @@
     @Override
     @XmlElement(name = "rationale")
     public InternationalString getRationale() {
-        return rationale;
+        return LegacyPropertyAdapter.getSingleton(rationales, InternationalString.class, null,
+                DefaultExtendedElementInformation.class, "getRationale");
     }
 
     /**
@@ -565,8 +566,7 @@
      * @since 0.5
      */
     public void setRationale(final InternationalString newValue) {
-        checkWritePermission(rationale);
-        rationale = newValue;
+        rationales = writeCollection(CollectionsExt.singletonOrEmpty(newValue), rationales, InternationalString.class);
     }
 
     /**
@@ -617,21 +617,31 @@
     /**
      * Name of the person or organization creating the extended element.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * As of ISO 19115:2014, {@code ResponsibleParty} is replaced by the {@code Responsibility} parent interface.
+     * This change may be applied in GeoAPI 4.0.
+     * </div>
+     *
      * @return name of the person or organization creating the extended element.
      */
     @Override
     @XmlElement(name = "source", required = true)
-    public Collection<Responsibility> getSources() {
-        return sources = nonNullCollection(sources, Responsibility.class);
+    public Collection<ResponsibleParty> getSources() {
+        return sources = nonNullCollection(sources, ResponsibleParty.class);
     }
 
     /**
      * Sets the name of the person or organization creating the extended element.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * As of ISO 19115:2014, {@code ResponsibleParty} is replaced by the {@code Responsibility} parent interface.
+     * This change may be applied in GeoAPI 4.0.
+     * </div>
+     *
      * @param  newValues  the new sources.
      */
-    public void setSources(final Collection<? extends Responsibility> newValues) {
-        sources = writeCollection(newValues, sources, Responsibility.class);
+    public void setSources(final Collection<? extends ResponsibleParty> newValues) {
+        sources = writeCollection(newValues, sources, ResponsibleParty.class);
     }
 
 
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 408a767..3a0b9aa 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
@@ -83,12 +83,12 @@
 import org.apache.sis.converter.SurjectiveConverter;
 import org.apache.sis.math.FunctionProperty;
 
+// 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 geoapi-4.0 branch:
-import org.opengis.metadata.citation.Responsibility;
-
 
 /**
  * Root entity which defines metadata about a resource or resources.
@@ -205,7 +205,7 @@
     /**
      * Serial number for inter-operability with different versions.
      */
-    private static final long serialVersionUID = -1128741312274891545L;
+    private static final long serialVersionUID = -76483485174667242L;
 
     /**
      * Language(s) and character set(s) used within the dataset.
@@ -229,7 +229,7 @@
      * Parties responsible for the metadata information.
      */
     @SuppressWarnings("serial")
-    private Collection<Responsibility> contacts;
+    private Collection<ResponsibleParty> contacts;
 
     /**
      * Date(s) associated with the metadata.
@@ -296,7 +296,7 @@
      * Provides information about the distributor of and options for obtaining the resource(s).
      */
     @SuppressWarnings("serial")
-    private Collection<Distribution> distributionInfo;
+    private Distribution distributionInfo;
 
     /**
      * Provides overall assessment of quality of a resource(s).
@@ -353,11 +353,11 @@
      * @param dateStamp           date that the metadata was created.
      * @param identificationInfo  basic information about the resource to which the metadata applies.
      */
-    public DefaultMetadata(final Responsibility contact,
+    public DefaultMetadata(final ResponsibleParty contact,
                            final Date           dateStamp,
                            final Identification identificationInfo)
     {
-        this.contacts  = singleton(contact, Responsibility.class);
+        this.contacts  = singleton(contact, ResponsibleParty.class);
         this.identificationInfo = singleton(identificationInfo, Identification.class);
         if (dateStamp != null) {
             dateInfo = singleton(new DefaultCitationDate(dateStamp, DateType.CREATION), CitationDate.class);
@@ -380,7 +380,7 @@
             parentMetadata                = object.getParentMetadata();
             locales                       = copyMap       (object.getLocalesAndCharsets(),            Locale.class);
             metadataScopes                = copyCollection(object.getMetadataScopes(),                MetadataScope.class);
-            contacts                      = copyCollection(object.getContacts(),                      Responsibility.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);
@@ -391,7 +391,7 @@
             metadataExtensionInfo         = copyCollection(object.getMetadataExtensionInfo(),         MetadataExtensionInformation.class);
             identificationInfo            = copyCollection(object.getIdentificationInfo(),            Identification.class);
             contentInfo                   = copyCollection(object.getContentInfo(),                   ContentInformation.class);
-            distributionInfo              = copyCollection(object.getDistributionInfo(),              Distribution.class);
+            distributionInfo              = object.getDistributionInfo();
             dataQualityInfo               = copyCollection(object.getDataQualityInfo(),               DataQuality.class);
             portrayalCatalogueInfo        = copyCollection(object.getPortrayalCatalogueInfo(),        PortrayalCatalogueReference.class);
             metadataConstraints           = copyCollection(object.getMetadataConstraints(),           Constraints.class);
@@ -908,12 +908,17 @@
     /**
      * Returns the parties responsible for the metadata information.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * As of ISO 19115:2014, {@code ResponsibleParty} is replaced by the {@code Responsibility} parent interface.
+     * This change will be tentatively applied in GeoAPI 4.0.
+     * </div>
+     *
      * @return parties responsible for the metadata information.
      */
     @Override
     @XmlElement(name = "contact", required = true)
-    public Collection<Responsibility> getContacts() {
-        return contacts = nonNullCollection(contacts, Responsibility.class);
+    public Collection<ResponsibleParty> getContacts() {
+        return contacts = nonNullCollection(contacts, ResponsibleParty.class);
     }
 
     /**
@@ -921,8 +926,8 @@
      *
      * @param  newValues  the new contacts.
      */
-    public void setContacts(final Collection<? extends Responsibility> newValues) {
-        contacts = writeCollection(newValues, contacts, Responsibility.class);
+    public void setContacts(final Collection<? extends ResponsibleParty> newValues) {
+        contacts = writeCollection(newValues, contacts, ResponsibleParty.class);
     }
 
     /**
@@ -1408,21 +1413,32 @@
     /**
      * Returns information about the distributor of and options for obtaining the resource(s).
      *
+     * <div class="warning"><b>Upcoming API change — multiplicity</b><br>
+     * As of ISO 19115:2014, this singleton has been replaced by a collection.
+     * This change will tentatively be applied in GeoAPI 4.0.
+     * </div>
+     *
      * @return the distributor of and options for obtaining the resource(s).
      */
     @Override
     @XmlElement(name = "distributionInfo")
-    public Collection<Distribution> getDistributionInfo() {
-        return distributionInfo = nonNullCollection(distributionInfo, Distribution.class);
+    public Distribution getDistributionInfo() {
+        return distributionInfo;
     }
 
     /**
      * Sets information about the distributor of and options for obtaining the resource(s).
      *
-     * @param  newValues  the new distribution info.
+     * <div class="warning"><b>Upcoming API change — multiplicity</b><br>
+     * As of ISO 19115:2014, this singleton has been replaced by a collection.
+     * This change will tentatively be applied in GeoAPI 4.0.
+     * </div>
+     *
+     * @param  newValue  the new distribution info.
      */
-    public void setDistributionInfo(final Collection<? extends Distribution> newValues) {
-        distributionInfo = writeCollection(newValues, distributionInfo, Distribution.class);
+    public void setDistributionInfo(final Distribution newValue) {
+        checkWritePermission(distributionInfo);
+        distributionInfo = newValue;
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/OnlineResourceAdapter.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/OnlineResourceAdapter.java
new file mode 100644
index 0000000..a66af47
--- /dev/null
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/OnlineResourceAdapter.java
@@ -0,0 +1,64 @@
+/*
+ * 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.iso;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import jakarta.xml.bind.annotation.adapters.XmlAdapter;
+import org.opengis.metadata.citation.OnlineResource;
+import org.apache.sis.xml.bind.metadata.CI_OnlineResource;
+import org.apache.sis.metadata.iso.citation.DefaultOnlineResource;
+
+
+/**
+ * Converts an URI to a {@code <cit:OnlineResource>} element for ISO 19115-3:2016 compliance.
+ * We need this additional adapter because some property type changed from {@code URI} to
+ * {@code OnlineResource} in the upgrade from ISO 19115:2003 to ISO 19115-1:2014.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ */
+final class OnlineResourceAdapter extends XmlAdapter<CI_OnlineResource, URI> {
+    /**
+     * The adapter performing the actual work.
+     */
+    private static final CI_OnlineResource ADAPTER = new CI_OnlineResource.Since2014();
+
+    /**
+     * Wraps the given URI in a {@code <cit:OnlineResource>} element.
+     */
+    @Override
+    public CI_OnlineResource marshal(final URI value) {
+        if (value != null) {
+            return ADAPTER.marshal(new DefaultOnlineResource(value));
+        }
+        return null;
+    }
+
+    /**
+     * Returns a URI from the given {@code <cit:OnlineResource>} element.
+     */
+    @Override
+    public URI unmarshal(final CI_OnlineResource value) throws URISyntaxException {
+        if (value != null) {
+            final OnlineResource res = ADAPTER.unmarshal(value);
+            if (res != null) {
+                return res.getLinkage();
+            }
+        }
+        return null;
+    }
+}
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/URIStringAdapter.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/URIStringAdapter.java
new file mode 100644
index 0000000..8e8dbe3
--- /dev/null
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/URIStringAdapter.java
@@ -0,0 +1,65 @@
+/*
+ * 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.iso;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import jakarta.xml.bind.annotation.adapters.XmlAdapter;
+import org.apache.sis.xml.bind.Context;
+import org.apache.sis.xml.bind.gco.CharSequenceAdapter;
+import org.apache.sis.xml.bind.gco.GO_CharacterString;
+
+
+/**
+ * Converts an URI to a {@code <gco:CharacterSequence>} element for ISO 19115-3:2016 compliance.
+ * We need this additional adapter because some property type changed from {@code URI}
+ * to {@code CharacterSequence} in the upgrade from ISO 19115:2003 to ISO 19115-1:2014.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ */
+final class URIStringAdapter extends XmlAdapter<GO_CharacterString, URI> {
+    /**
+     * The adapter performing the actual work.
+     */
+    private static final CharSequenceAdapter ADAPTER = new CharSequenceAdapter.Since2014();
+
+    /**
+     * Wraps the given URI in a {@code <cit:OnlineResource>} element.
+     */
+    @Override
+    public GO_CharacterString marshal(final URI value) {
+        if (value != null) {
+            return ADAPTER.marshal(value.toString());
+        }
+        return null;
+    }
+
+    /**
+     * Returns a URI from the given {@code <cit:OnlineResource>} element.
+     */
+    @Override
+    public URI unmarshal(final GO_CharacterString value) throws URISyntaxException {
+        if (value != null) {
+            final CharSequence uri = ADAPTER.unmarshal(value);
+            if (uri != null) {
+                final Context context = Context.current();
+                return Context.converter(context).toURI(context, uri.toString());
+            }
+        }
+        return null;
+    }
+}
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 c2877a8..355f11c 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
@@ -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.getObjectiveOccurrences(), Event.class);
+            objectiveOccurrences = copyCollection(object.getObjectiveOccurences(), Event.class);
             pass                 = copyCollection(object.getPass(), PlatformPass.class);
             sensingInstruments   = copyCollection(object.getSensingInstruments(), Instrument.class);
         }
@@ -296,23 +296,47 @@
      * Returns the event or events associated with objective completion.
      *
      * @return events associated with objective completion.
+     *
+     * @since 1.0
      */
-    @Override
     @XmlElement(name = "objectiveOccurence", required = true)
     public Collection<Event> getObjectiveOccurrences() {
         return objectiveOccurrences = nonNullCollection(objectiveOccurrences, Event.class);
     }
 
     /**
+     * @deprecated Renamed {@link #getObjectiveOccurrences()}.
+     *
+     * @return events associated with objective completion.
+     */
+    @Override
+    @Deprecated
+    public Collection<Event> getObjectiveOccurences() {
+        return getObjectiveOccurrences();
+    }
+
+    /**
      * 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)}.
+     *
+     * @param  newValues  the new objective occurrences values.
+     */
+    @Deprecated
+    public void setObjectiveOccurences(final Collection<? extends Event> newValues) {
+        setObjectiveOccurrences(newValues);
+    }
+
+    /**
      * Returns the pass of the platform over the objective.
      *
      * @return pass of the platform.
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/acquisition/DefaultPlatform.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/acquisition/DefaultPlatform.java
index 0dacd87..ab02dcb 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/acquisition/DefaultPlatform.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/acquisition/DefaultPlatform.java
@@ -27,8 +27,8 @@
 import org.opengis.util.InternationalString;
 import org.apache.sis.metadata.iso.ISOMetadata;
 
-// Specific to the geoapi-4.0 branch:
-import org.opengis.metadata.citation.Responsibility;
+// Specific to the main and geoapi-3.1 branches:
+import org.opengis.metadata.citation.ResponsibleParty;
 
 
 /**
@@ -87,7 +87,7 @@
      * Organization responsible for building, launch, or operation of the platform.
      */
     @SuppressWarnings("serial")
-    private Collection<Responsibility> sponsors;
+    private Collection<ResponsibleParty> sponsors;
 
     /**
      * Instrument(s) mounted on a platform.
@@ -116,7 +116,7 @@
             citation    = object.getCitation();
             identifiers = singleton(object.getIdentifier(), Identifier.class);
             description = object.getDescription();
-            sponsors    = copyCollection(object.getSponsors(), Responsibility.class);
+            sponsors    = copyCollection(object.getSponsors(), ResponsibleParty.class);
             instruments = copyCollection(object.getInstruments(), Instrument.class);
         }
     }
@@ -212,21 +212,31 @@
     /**
      * Returns the organization responsible for building, launch, or operation of the platform.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * As of ISO 19115:2014, {@code ResponsibleParty} is replaced by the {@code Responsibility} parent interface.
+     * This change will be tentatively applied in GeoAPI 4.0.
+     * </div>
+     *
      * @return organization responsible for building, launch, or operation of the platform.
      */
     @Override
     @XmlElement(name = "sponsor")
-    public Collection<Responsibility> getSponsors() {
-        return sponsors = nonNullCollection(sponsors, Responsibility.class);
+    public Collection<ResponsibleParty> getSponsors() {
+        return sponsors = nonNullCollection(sponsors, ResponsibleParty.class);
     }
 
     /**
      * Sets the organization responsible for building, launch, or operation of the platform.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * As of ISO 19115:2014, {@code ResponsibleParty} is replaced by the {@code Responsibility} parent interface.
+     * This change will be tentatively applied in GeoAPI 4.0.
+     * </div>
+     *
      * @param  newValues  the new sponsors values;
      */
-    public void setSponsors(final Collection<? extends Responsibility> newValues) {
-        sponsors = writeCollection(newValues, sponsors, Responsibility.class);
+    public void setSponsors(final Collection<? extends ResponsibleParty> newValues) {
+        sponsors = writeCollection(newValues, sponsors, ResponsibleParty.class);
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/acquisition/DefaultRequirement.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/acquisition/DefaultRequirement.java
index bfc73b1..7436c35 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/acquisition/DefaultRequirement.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/acquisition/DefaultRequirement.java
@@ -31,8 +31,8 @@
 import static org.apache.sis.metadata.privy.ImplementationHelper.toDate;
 import static org.apache.sis.metadata.privy.ImplementationHelper.toMilliseconds;
 
-// Specific to the geoapi-4.0 branch:
-import org.opengis.metadata.citation.Responsibility;
+// Specific to the main and geoapi-3.1 branches:
+import org.opengis.metadata.citation.ResponsibleParty;
 
 
 /**
@@ -95,13 +95,13 @@
      * Origin of requirement.
      */
     @SuppressWarnings("serial")
-    private Collection<Responsibility> requestors;
+    private Collection<ResponsibleParty> requestors;
 
     /**
      * Person(s), or body(ies), to receive results of requirement.
      */
     @SuppressWarnings("serial")
-    private Collection<Responsibility> recipients;
+    private Collection<ResponsibleParty> recipients;
 
     /**
      * Relative ordered importance, or urgency, of the requirement.
@@ -146,8 +146,8 @@
         if (object != null) {
             citation       = object.getCitation();
             identifiers    = singleton(object.getIdentifier(), Identifier.class);
-            requestors     = copyCollection(object.getRequestors(), Responsibility.class);
-            recipients     = copyCollection(object.getRecipients(), Responsibility.class);
+            requestors     = copyCollection(object.getRequestors(), ResponsibleParty.class);
+            recipients     = copyCollection(object.getRecipients(), ResponsibleParty.class);
             priority       = object.getPriority();
             requestedDate  = object.getRequestedDate();
             expiryDate     = toMilliseconds(object.getExpiryDate());
@@ -226,41 +226,61 @@
     /**
      * Returns the origin of requirement.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * As of ISO 19115:2014, {@code ResponsibleParty} is replaced by the {@code Responsibility} parent interface.
+     * This change will be tentatively applied in GeoAPI 4.0.
+     * </div>
+     *
      * @return origin of requirement.
      */
     @Override
     @XmlElement(name = "requestor", required = true)
-    public Collection<Responsibility> getRequestors() {
-        return requestors = nonNullCollection(requestors, Responsibility.class);
+    public Collection<ResponsibleParty> getRequestors() {
+        return requestors = nonNullCollection(requestors, ResponsibleParty.class);
     }
 
     /**
      * Sets the origin of requirement.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * As of ISO 19115:2014, {@code ResponsibleParty} is replaced by the {@code Responsibility} parent interface.
+     * This change will be tentatively applied in GeoAPI 4.0.
+     * </div>
+     *
      * @param  newValues  the new requestors values.
      */
-    public void setRequestors(final Collection<? extends Responsibility> newValues) {
-        requestors = writeCollection(newValues, requestors, Responsibility.class);
+    public void setRequestors(final Collection<? extends ResponsibleParty> newValues) {
+        requestors = writeCollection(newValues, requestors, ResponsibleParty.class);
     }
 
     /**
      * Returns the person(s), or body(ies), to receive results of requirement.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * As of ISO 19115:2014, {@code ResponsibleParty} is replaced by the {@code Responsibility} parent interface.
+     * This change will be tentatively applied in GeoAPI 4.0.
+     * </div>
+     *
      * @return person(s), or body(ies), to receive results.
      */
     @Override
     @XmlElement(name = "recipient", required = true)
-    public Collection<Responsibility> getRecipients() {
-        return recipients = nonNullCollection(recipients, Responsibility.class);
+    public Collection<ResponsibleParty> getRecipients() {
+        return recipients = nonNullCollection(recipients, ResponsibleParty.class);
     }
 
     /**
      * Sets the Person(s), or body(ies), to receive results of requirement.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * As of ISO 19115:2014, {@code ResponsibleParty} is replaced by the {@code Responsibility} parent interface.
+     * This change will be tentatively applied in GeoAPI 4.0.
+     * </div>
+     *
      * @param  newValues  the new recipients values.
      */
-    public void setRecipients(final Collection<? extends Responsibility> newValues) {
-        recipients = writeCollection(newValues, recipients, Responsibility.class);
+    public void setRecipients(final Collection<? extends ResponsibleParty> newValues) {
+        recipients = writeCollection(newValues, recipients, ResponsibleParty.class);
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/acquisition/package-info.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/acquisition/package-info.java
index eb390c7..a9470eb 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/acquisition/package-info.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/acquisition/package-info.java
@@ -99,7 +99,7 @@
 @XmlAccessorType(XmlAccessType.NONE)
 @XmlJavaTypeAdapters({
     @XmlJavaTypeAdapter(CI_Citation.class),
-    @XmlJavaTypeAdapter(CI_Responsibility.class),
+    @XmlJavaTypeAdapter(CI_ResponsibleParty.class),
     @XmlJavaTypeAdapter(EX_Extent.class),
     @XmlJavaTypeAdapter(GO_DateTime.class),
     @XmlJavaTypeAdapter(GO_Real.class),
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 f7fd6e4..76f50f1 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
@@ -85,7 +85,7 @@
      * Address line for the location (as described in ISO 11180, Annex A).
      */
     @SuppressWarnings("serial")
-    private Collection<InternationalString> deliveryPoints;
+    private Collection<String> deliveryPoints;
 
     /**
      * Address of the electronic mailbox of the responsible organization or individual.
@@ -111,7 +111,7 @@
     public DefaultAddress(final Address object) {
         super(object);
         if (object != null) {
-            deliveryPoints          = copyCollection(object.getDeliveryPoints(), InternationalString.class);
+            deliveryPoints          = copyCollection(object.getDeliveryPoints(), String.class);
             city                    = object.getCity();
             administrativeArea      = object.getAdministrativeArea();
             postalCode              = object.getPostalCode();
@@ -211,21 +211,31 @@
     /**
      * Returns the address line for the location (as described in ISO 11180, Annex A).
      *
+     * <div class="warning"><b>Upcoming API change — internationalization</b><br>
+     * The return type may be changed from {@code Collection<String>} to
+     * {@code Collection<? extends InternationalString>} in GeoAPI 4.0.
+     * </div>
+     *
      * @return address line for the location.
      */
     @Override
     @XmlElement(name = "deliveryPoint")
-    public Collection<InternationalString> getDeliveryPoints() {
-        return deliveryPoints = nonNullCollection(deliveryPoints, InternationalString.class);
+    public Collection<String> getDeliveryPoints() {
+        return deliveryPoints = nonNullCollection(deliveryPoints, String.class);
     }
 
     /**
      * Sets the address line for the location (as described in ISO 11180, Annex A).
      *
+     * <div class="warning"><b>Upcoming API change — internationalization</b><br>
+     * The argument type may be changed from {@code Collection<String>} to
+     * {@code Collection<? extends InternationalString>} in GeoAPI 4.0.
+     * </div>
+     *
      * @param  newValues  the new delivery points, or {@code null} if none.
      */
-    public void setDeliveryPoints(final Collection<? extends InternationalString> newValues) {
-        deliveryPoints = writeCollection(newValues, deliveryPoints, InternationalString.class);
+    public void setDeliveryPoints(final Collection<? extends String> newValues) {
+        deliveryPoints = writeCollection(newValues, deliveryPoints, String.class);
     }
 
     /**
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 24ae2c5..4ed38a3 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
@@ -41,8 +41,8 @@
 import static org.apache.sis.metadata.privy.ImplementationHelper.toDate;
 import static org.apache.sis.metadata.privy.ImplementationHelper.toMilliseconds;
 
-// Specific to the geoapi-4.0 branch:
-import org.opengis.metadata.citation.Responsibility;
+// Specific to the main and geoapi-3.1 branches:
+import org.opengis.metadata.citation.ResponsibleParty;
 
 
 /**
@@ -98,7 +98,7 @@
     /**
      * Serial number for inter-operability with different versions.
      */
-    private static final long serialVersionUID = -7343644724857519090L;
+    private static final long serialVersionUID = 3490090845236158848L;
 
     /**
      * Name by which the cited resource is known.
@@ -136,7 +136,7 @@
      * for the resource.
      */
     @SuppressWarnings("serial")
-    private Collection<Responsibility> citedResponsibleParties;
+    private Collection<ResponsibleParty> citedResponsibleParties;
 
     /**
      * Mode in which the resource is represented, or an empty collection if none.
@@ -156,7 +156,7 @@
      * May be {@code null} if none.
      */
     @SuppressWarnings("serial")
-    private Collection<InternationalString> otherCitationDetails;
+    private InternationalString otherCitationDetails;
 
     /**
      * Common title with holdings note. Note: title identifies elements of a series
@@ -216,10 +216,10 @@
             edition                 = object.getEdition();
             editionDate             = toMilliseconds(object.getEditionDate());
             identifiers             = copyCollection(object.getIdentifiers(), Identifier.class);
-            citedResponsibleParties = copyCollection(object.getCitedResponsibleParties(), Responsibility.class);
+            citedResponsibleParties = copyCollection(object.getCitedResponsibleParties(), ResponsibleParty.class);
             presentationForms       = copyCollection(object.getPresentationForms(), PresentationForm.class);
             series                  = object.getSeries();
-            otherCitationDetails    = copyCollection(object.getOtherCitationDetails(), InternationalString.class);
+            otherCitationDetails    = object.getOtherCitationDetails();
             collectiveTitle         = object.getCollectiveTitle();
             onlineResources         = copyCollection(object.getOnlineResources(), OnlineResource.class);
             graphics                = copyCollection(object.getGraphics(), BrowseGraphic.class);
@@ -417,22 +417,32 @@
      * Returns the role, name, contact and position information for an individual or organization
      * that is responsible for the resource.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * As of ISO 19115:2014, {@code ResponsibleParty} is replaced by the {@code Responsibility} parent interface.
+     * This change may be applied in GeoAPI 4.0.
+     * </div>
+     *
      * @return the individual or organization that is responsible, or an empty collection if none.
      */
     @Override
     @XmlElement(name = "citedResponsibleParty")
-    public Collection<Responsibility> getCitedResponsibleParties() {
-        return citedResponsibleParties = nonNullCollection(citedResponsibleParties, Responsibility.class);
+    public Collection<ResponsibleParty> getCitedResponsibleParties() {
+        return citedResponsibleParties = nonNullCollection(citedResponsibleParties, ResponsibleParty.class);
     }
 
     /**
      * Sets the role, name, contact and position information for an individual or organization
      * that is responsible for the resource.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * As of ISO 19115:2014, {@code ResponsibleParty} is replaced by the {@code Responsibility} parent interface.
+     * This change may be applied in GeoAPI 4.0.
+     * </div>
+     *
      * @param  newValues  the new cited responsible parties, or {@code null} if none.
      */
-    public void setCitedResponsibleParties(final Collection<? extends Responsibility> newValues) {
-        citedResponsibleParties = writeCollection(newValues, citedResponsibleParties, Responsibility.class);
+    public void setCitedResponsibleParties(final Collection<? extends ResponsibleParty> newValues) {
+        citedResponsibleParties = writeCollection(newValues, citedResponsibleParties, ResponsibleParty.class);
     }
 
     /**
@@ -479,21 +489,32 @@
     /**
      * Returns other information required to complete the citation that is not recorded elsewhere.
      *
+     * <div class="warning"><b>Upcoming API change — multiplicity</b><br>
+     * As of ISO 19115:2014, this singleton has been replaced by a collection.
+     * This change may be applied in GeoAPI 4.0.
+     * </div>
+     *
      * @return other details, or {@code null} if none.
      */
     @Override
     @XmlElement(name = "otherCitationDetails")
-    public Collection<InternationalString> getOtherCitationDetails() {
-        return otherCitationDetails = nonNullCollection(otherCitationDetails, InternationalString.class);
+    public InternationalString getOtherCitationDetails() {
+        return otherCitationDetails;
     }
 
     /**
      * Sets other information required to complete the citation that is not recorded elsewhere.
      *
-     * @param newValues Other citations details.
+     * <div class="warning"><b>Upcoming API change — multiplicity</b><br>
+     * As of ISO 19115:2014, this singleton has been replaced by a collection.
+     * This change may be applied in GeoAPI 4.0.
+     * </div>
+     *
+     * @param newValue Other citations details, or {@code null} if none.
      */
-    public void setOtherCitationDetails(final Collection<? extends InternationalString> newValues) {
-        otherCitationDetails = writeCollection(newValues, otherCitationDetails, InternationalString.class);
+    public void setOtherCitationDetails(final InternationalString newValue) {
+        checkWritePermission(otherCitationDetails);
+        otherCitationDetails = newValue;
     }
 
     /**
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 2f27187..99f6a6b 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
@@ -102,7 +102,7 @@
      * Time period (including time zone) when individuals can contact the organization or individual.
      */
     @SuppressWarnings("serial")
-    private Collection<InternationalString> hoursOfService;
+    private InternationalString hoursOfService;
 
     /**
      * Supplemental instructions on how or when to contact the individual or organization.
@@ -147,7 +147,7 @@
             phones              = copyCollection(object.getPhones(), Telephone.class);
             addresses           = copyCollection(object.getAddresses(), Address.class);
             onlineResources     = copyCollection(object.getOnlineResources(), OnlineResource.class);
-            hoursOfService      = copyCollection(object.getHoursOfService(), InternationalString.class);
+            hoursOfService      = object.getHoursOfService();
             contactInstructions = object.getContactInstructions();
             contactType         = object.getContactType();
         }
@@ -407,21 +407,32 @@
     /**
      * Returns the time period (including time zone) when individuals can contact the organization or individual.
      *
+     * <div class="warning"><b>Upcoming API change — multiplicity</b><br>
+     * As of ISO 19115:2014, this singleton has been replaced by a collection.
+     * This change will tentatively be applied in GeoAPI 4.0.
+     * </div>
+     *
      * @return time period when individuals can contact the organization or individual.
      */
     @Override
     @XmlElement(name = "hoursOfService")
-    public Collection<InternationalString> getHoursOfService() {
-        return hoursOfService = nonNullCollection(hoursOfService, InternationalString.class);
+    public InternationalString getHoursOfService() {
+        return hoursOfService;
     }
 
     /**
      * Sets time period (including time zone) when individuals can contact the organization or individual.
      *
-     * @param  newValues  the new hours of service.
+     * <div class="warning"><b>Upcoming API change — multiplicity</b><br>
+     * As of ISO 19115:2014, this singleton has been replaced by a collection.
+     * This change will tentatively be applied in GeoAPI 4.0.
+     * </div>
+     *
+     * @param  newValue  the new hours of service.
      */
-    public void setHoursOfService(final Collection<? extends InternationalString> newValues) {
-        hoursOfService = writeCollection(newValues, hoursOfService, InternationalString.class);
+    public void setHoursOfService(final InternationalString newValue) {
+        checkWritePermission(hoursOfService);
+        hoursOfService = newValue;
     }
 
     /**
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 7136ec7..c190272 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
@@ -89,7 +89,7 @@
      * Name of the online resources.
      */
     @SuppressWarnings("serial")
-    private InternationalString name;
+    private String name;
 
     /**
      * Detailed text description of what the online resource is/does.
@@ -196,20 +196,28 @@
     /**
      * Name of the online resource. Returns {@code null} if none.
      *
+     * <div class="warning"><b>Upcoming API change — internationalization</b><br>
+     * The return type may be changed from {@code String} to {@code InternationalString} in GeoAPI 4.0.
+     * </div>
+     *
      * @return name of the online resource, or {@code null}.
      */
     @Override
     @XmlElement(name = "name")
-    public InternationalString getName() {
+    public String getName() {
         return name;
     }
 
     /**
      * Sets the name of the online resource.
      *
+     * <div class="warning"><b>Upcoming API change — internationalization</b><br>
+     * The argument type may be changed from {@code String} to {@code InternationalString} in GeoAPI 4.0.
+     * </div>
+     *
      * @param  newValue  the new name, or {@code null} if none.
      */
-    public void setName(final InternationalString newValue) {
+    public void setName(final String newValue) {
         checkWritePermission(name);
         name = newValue;
     }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/DefaultSeries.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/DefaultSeries.java
index 128e31e..b87369e 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/DefaultSeries.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/DefaultSeries.java
@@ -52,7 +52,7 @@
     /**
      * Serial number for inter-operability with different versions.
      */
-    private static final long serialVersionUID = -1584743260325409070L;
+    private static final long serialVersionUID = 7061644572814855051L;
 
     /**
      * Name of the series, or aggregate dataset, of which the dataset is a part.
@@ -64,13 +64,13 @@
      * Information identifying the issue of the series.
      */
     @SuppressWarnings("serial")
-    private InternationalString issueIdentification;
+    private String issueIdentification;
 
     /**
      * Details on which pages of the publication the article was published.
      */
     @SuppressWarnings("serial")
-    private InternationalString page;
+    private String page;
 
     /**
      * Constructs a default series.
@@ -154,20 +154,30 @@
     /**
      * Returns information identifying the issue of the series.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * As of ISO 19115:2014, {@code String} is replaced by the {@link InternationalString} interface.
+     * This change will be tentatively applied in GeoAPI 4.0.
+     * </div>
+     *
      * @return information identifying the issue of the series, or {@code null}.
      */
     @Override
     @XmlElement(name = "issueIdentification")
-    public InternationalString getIssueIdentification() {
+    public String getIssueIdentification() {
         return issueIdentification;
     }
 
     /**
      * Sets information identifying the issue of the series.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * As of ISO 19115:2014, {@code String} is replaced by the {@link InternationalString} interface.
+     * This change will be tentatively applied in GeoAPI 4.0.
+     * </div>
+     *
      * @param  newValue  the new issue identification, or {@code null} if none.
      */
-    public void setIssueIdentification(final InternationalString newValue) {
+    public void setIssueIdentification(final String newValue) {
         checkWritePermission(issueIdentification);
         issueIdentification = newValue;
     }
@@ -175,20 +185,30 @@
     /**
      * Returns details on which pages of the publication the article was published.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * As of ISO 19115:2014, {@code String} is replaced by the {@link InternationalString} interface.
+     * This change will be tentatively applied in GeoAPI 4.0.
+     * </div>
+     *
      * @return details on which pages of the publication the article was published, or {@code null}.
      */
     @Override
     @XmlElement(name = "page")
-    public InternationalString getPage() {
+    public String getPage() {
         return page;
     }
 
     /**
      * Sets details on which pages of the publication the article was published.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * As of ISO 19115:2014, {@code String} is replaced by the {@link InternationalString} interface.
+     * This change will be tentatively applied in GeoAPI 4.0.
+     * </div>
+     *
      * @param  newValue  the new page, or {@code null} if none.
      */
-    public void setPage(final InternationalString newValue) {
+    public void setPage(final String newValue) {
         checkWritePermission(page);
         page = newValue;
     }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/package-info.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/package-info.java
index 7637116..3d33901 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/package-info.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/package-info.java
@@ -107,7 +107,7 @@
     @XmlJavaTypeAdapter(CI_OnlineResource.class),
     @XmlJavaTypeAdapter(CI_Party.class),
     @XmlJavaTypeAdapter(CI_PresentationFormCode.class),
-    @XmlJavaTypeAdapter(CI_Responsibility.class),
+    @XmlJavaTypeAdapter(CI_ResponsibleParty.class),
     @XmlJavaTypeAdapter(CI_RoleCode.class),
     @XmlJavaTypeAdapter(CI_Series.class),
     @XmlJavaTypeAdapter(CI_Telephone.class),
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 f98ad5b..546a873 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
@@ -24,10 +24,10 @@
 import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
 import org.opengis.util.InternationalString;
 import org.opengis.metadata.citation.Citation;
+import org.opengis.metadata.identification.BrowseGraphic;
 import org.opengis.metadata.constraint.Constraints;
 import org.opengis.metadata.constraint.LegalConstraints;
 import org.opengis.metadata.constraint.SecurityConstraints;
-import org.opengis.metadata.identification.BrowseGraphic;
 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;
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 179f428..a8898f2 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
@@ -31,8 +31,8 @@
 import org.apache.sis.xml.bind.gco.UnitAdapter;
 import static org.apache.sis.metadata.privy.ImplementationHelper.ensurePositive;
 
-// Specific to the geoapi-4.0 branch:
-import org.opengis.metadata.content.PolarisationOrientation;
+// Specific to the main and geoapi-3.1 branches:
+import org.opengis.metadata.content.PolarizationOrientation;
 
 
 /**
@@ -69,8 +69,8 @@
     "bandBoundaryDefinition",
     "nominalSpatialResolution",
     "transferFunctionType",
-    "transmittedPolarisation",
-    "detectedPolarisation"
+    "transmittedPolarization",
+    "detectedPolarization"
 })
 @XmlRootElement(name = "MD_Band")
 @XmlSeeAlso(org.apache.sis.xml.bind.gmi.MI_Band.class)
@@ -78,7 +78,7 @@
     /**
      * Serial number for inter-operability with different versions.
      */
-    private static final long serialVersionUID = 2492553738885938445L;
+    private static final long serialVersionUID = -2474871120376144737L;
 
     /**
      * Shortest wavelength that the sensor is capable of collecting within a designated band.
@@ -114,12 +114,12 @@
     /**
      * Polarization of the radiation transmitted.
      */
-    private PolarisationOrientation transmittedPolarisation;
+    private PolarizationOrientation transmittedPolarization;
 
     /**
      * Polarization of the radiation detected.
      */
-    private PolarisationOrientation detectedPolarisation;
+    private PolarizationOrientation detectedPolarization;
 
     /**
      * Constructs an initially empty band.
@@ -151,8 +151,8 @@
             peakResponse             = object.getPeakResponse();
             toneGradation            = object.getToneGradation();
             bandBoundaryDefinition   = object.getBandBoundaryDefinition();
-            transmittedPolarisation  = object.getTransmittedPolarisation();
-            detectedPolarisation     = object.getDetectedPolarisation();
+            transmittedPolarization  = object.getTransmittedPolarization();
+            detectedPolarization     = object.getDetectedPolarization();
         }
     }
 
@@ -387,42 +387,62 @@
     /**
      * Returns the polarization of the radiation transmitted.
      *
+     * <div class="warning"><b>Upcoming API change</b><br>
+     * This method may be renamed {@code getTransmittedPolarization} and its return type replaced by
+     * {@code PolarisationOrientation} ("z" letter replaced by "s" letter) in GeoAPI 4.0
+     * for compliance with ISO 19115-2:2019.</div>
+     *
      * @return polarization of the radiation transmitted, or {@code null}.
      */
     @Override
     @XmlElement(name = "transmittedPolarisation")
-    public PolarisationOrientation getTransmittedPolarisation() {
-        return transmittedPolarisation;
+    public PolarizationOrientation getTransmittedPolarization() {
+        return transmittedPolarization;
     }
 
     /**
      * Sets the polarization of the radiation transmitted.
      *
+     * <div class="warning"><b>Upcoming API change</b><br>
+     * This method may be renamed {@code setTransmittedPolarization} and its argument type replaced by
+     * {@code PolarisationOrientation} ("z" letter replaced by "s" letter) in GeoAPI 4.0
+     * for compliance with ISO 19115-2:2019.</div>
+     *
      * @param  newValue  the new transmitted polarization.
      */
-    public void setTransmittedPolarisation(final PolarisationOrientation newValue) {
-        checkWritePermission(transmittedPolarisation);
-        transmittedPolarisation = newValue;
+    public void setTransmittedPolarization(final PolarizationOrientation newValue) {
+        checkWritePermission(transmittedPolarization);
+        transmittedPolarization = newValue;
     }
 
     /**
      * Returns polarization of the radiation detected.
      *
+     * <div class="warning"><b>Upcoming API change</b><br>
+     * This method may be renamed {@code getDetectedPolarization} and its return type replaced by
+     * {@code PolarisationOrientation} ("z" letter replaced by "s" letter) in GeoAPI 4.0
+     * for compliance with ISO 19115-2:2019.</div>
+     *
      * @return polarization of the radiation detected, or {@code null}.
      */
     @Override
     @XmlElement(name = "detectedPolarisation")
-    public PolarisationOrientation getDetectedPolarisation() {
-        return detectedPolarisation;
+    public PolarizationOrientation getDetectedPolarization() {
+        return detectedPolarization;
     }
 
     /**
      * Sets the polarization of the radiation detected.
      *
+     * <div class="warning"><b>Upcoming API change</b><br>
+     * This method may be renamed {@code setDetectedPolarization} and its argument type replaced by
+     * {@code PolarisationOrientation} ("z" letter replaced by "s" letter) in GeoAPI 4.0
+     * for compliance with ISO 19115-2:2019.</div>
+     *
      * @param  newValue  the new detected polarization.
      */
-    public void setDetectedPolarisation(final PolarisationOrientation newValue) {
-        checkWritePermission(detectedPolarisation);
-        detectedPolarisation = newValue;
+    public void setDetectedPolarization(final PolarizationOrientation newValue) {
+        checkWritePermission(detectedPolarization);
+        detectedPolarization = newValue;
     }
 }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/content/package-info.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/content/package-info.java
index 7af0844..aafc111 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/content/package-info.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/content/package-info.java
@@ -44,7 +44,7 @@
  * {@code  ├─} {@linkplain org.opengis.metadata.content.BandDefinition          Band definition}<br>
  * {@code  ├─} {@linkplain org.opengis.metadata.content.CoverageContentType     Coverage content type}<br>
  * {@code  ├─} {@linkplain org.opengis.metadata.content.ImagingCondition        Imaging condition}<br>
- * {@code  ├─} {@linkplain org.opengis.metadata.content.PolarisationOrientation Polarisation orientation}<br>
+ * {@code  ├─} {@linkplain org.opengis.metadata.content.PolarizationOrientation Polarisation orientation}<br>
  * {@code  └─} {@linkplain org.opengis.metadata.content.TransferFunctionType    Transfer function type}<br>
  * </td><td class="sep" style="width: 50%; white-space: nowrap">
  *                 {@linkplain org.apache.sis.metadata.iso.content.AbstractContentInformation         Content information} «abstract»<br>
@@ -57,7 +57,7 @@
  * {@code  └─}     {@linkplain org.apache.sis.metadata.iso.content.DefaultRangeElementDescription     Range element description}<br>
  *                 {@linkplain org.apache.sis.metadata.iso.content.DefaultBand                        Band}<br>
  * {@code  ├─}     {@linkplain org.opengis.metadata.content.BandDefinition                            Band definition} «code list»<br>
- * {@code  ├─}     {@linkplain org.opengis.metadata.content.PolarisationOrientation                   Polarisation orientation} «code list»<br>
+ * {@code  ├─}     {@linkplain org.opengis.metadata.content.PolarizationOrientation                   Polarisation orientation} «code list»<br>
  * {@code  └─}     {@linkplain org.opengis.metadata.content.TransferFunctionType                      Transfer function type} «code list»<br>
  *                 {@linkplain org.apache.sis.metadata.iso.content.DefaultImageDescription            Image description}<br>
  * {@code  └─}     {@linkplain org.opengis.metadata.content.ImagingCondition                          Imaging condition} «code list»<br>
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 ef4bfb8..26c845b 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,17 +22,17 @@
 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;
 import org.apache.sis.xml.privy.LegacyNamespaces;
 import org.apache.sis.xml.bind.gcx.MimeFileTypeAdapter;
 
-// Specific to the geoapi-4.0 branch:
-import org.opengis.util.GenericName;
+// Specific to the main and geoapi-3.1 branches:
+import org.opengis.util.LocalName;
 
 
 /**
@@ -96,7 +96,7 @@
      * data does not relate to a feature catalogue.
      */
     @SuppressWarnings("serial")
-    private Collection<GenericName> featureTypes;
+    private Collection<LocalName> featureTypes;
 
     /**
      * Defines the format of the transfer data file.
@@ -129,7 +129,7 @@
             fileName        = object.getFileName();
             fileDescription = object.getFileDescription();
             fileType        = object.getFileType();
-            featureTypes    = copyCollection(object.getFeatureTypes(), GenericName.class);
+            featureTypes    = copyCollection(object.getFeatureTypes(), LocalName.class);
             fileFormat      = object.getFileFormat();
         }
     }
@@ -248,8 +248,8 @@
      */
     @Override
     @XmlElement(name = "featureTypes")
-    public Collection<GenericName> getFeatureTypes() {
-        return featureTypes = nonNullCollection(featureTypes, GenericName.class);
+    public Collection<LocalName> getFeatureTypes() {
+        return featureTypes = nonNullCollection(featureTypes, LocalName.class);
     }
 
     /**
@@ -257,8 +257,8 @@
      *
      * @param newValues  the new feature type values.
      */
-    public void setFeatureTypes(final Collection<? extends GenericName> newValues) {
-        featureTypes = writeCollection(newValues, featureTypes, GenericName.class);
+    public void setFeatureTypes(final Collection<? extends LocalName> newValues) {
+        featureTypes = writeCollection(newValues, featureTypes, LocalName.class);
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/distribution/DefaultDistributor.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/distribution/DefaultDistributor.java
index fc172f5..20be1b2 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/distribution/DefaultDistributor.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/distribution/DefaultDistributor.java
@@ -26,8 +26,8 @@
 import org.opengis.metadata.distribution.DigitalTransferOptions;
 import org.apache.sis.metadata.iso.ISOMetadata;
 
-// Specific to the geoapi-4.0 branch:
-import org.opengis.metadata.citation.Responsibility;
+// Specific to the main and geoapi-3.1 branches:
+import org.opengis.metadata.citation.ResponsibleParty;
 
 
 /**
@@ -71,13 +71,13 @@
     /**
      * Serial number for inter-operability with different versions.
      */
-    private static final long serialVersionUID = -8819538342342106743L;
+    private static final long serialVersionUID = 5706757156163948001L;
 
     /**
      * Party from whom the resource may be obtained. This list need not be exhaustive.
      */
     @SuppressWarnings("serial")
-    private Responsibility distributorContact;
+    private ResponsibleParty distributorContact;
 
     /**
      * Provides information about how the resource may be obtained, and related
@@ -109,7 +109,7 @@
      *
      * @param distributorContact  party from whom the resource may be obtained, or {@code null}.
      */
-    public DefaultDistributor(final Responsibility distributorContact) {
+    public DefaultDistributor(final ResponsibleParty distributorContact) {
         this.distributorContact = distributorContact;
     }
 
@@ -160,20 +160,30 @@
     /**
      * Party from whom the resource may be obtained.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * As of ISO 19115:2014, {@code ResponsibleParty} is replaced by the {@code Responsibility} parent interface.
+     * This change may be applied in GeoAPI 4.0.
+     * </div>
+     *
      * @return party from whom the resource may be obtained, or {@code null}.
      */
     @Override
     @XmlElement(name = "distributorContact", required = true)
-    public Responsibility getDistributorContact() {
+    public ResponsibleParty getDistributorContact() {
         return distributorContact;
     }
 
     /**
      * Sets the party from whom the resource may be obtained.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * As of ISO 19115:2014, {@code ResponsibleParty} is replaced by the {@code Responsibility} parent interface.
+     * This change may be applied in GeoAPI 4.0.
+     * </div>
+     *
      * @param  newValue  the new distributor contact.
      */
-    public void setDistributorContact(final Responsibility newValue) {
+    public void setDistributorContact(final ResponsibleParty newValue) {
         checkWritePermission(distributorContact);
         distributorContact = newValue;
     }
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 faf89f0..4b41ed3 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
@@ -33,19 +33,21 @@
 import org.apache.sis.xml.bind.gco.GO_Real;
 import org.apache.sis.xml.bind.metadata.CI_Citation;
 import org.apache.sis.xml.bind.metadata.MD_Identifier;
-import org.apache.sis.metadata.iso.legacy.LegacyPropertyAdapter;
 import org.apache.sis.metadata.internal.Dependencies;
+import org.apache.sis.metadata.iso.legacy.LegacyPropertyAdapter;
 import org.apache.sis.xml.privy.LegacyNamespaces;
 import org.apache.sis.util.privy.CollectionsExt;
 import static org.apache.sis.metadata.privy.ImplementationHelper.ensurePositive;
 
+// Specific to the main and geoapi-3.1 branches:
+import org.opengis.metadata.distribution.MediumName;
+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 geoapi-4.0 branch:
-import org.apache.sis.metadata.iso.legacy.MediumName;
-
 
 /**
  * Information about the media on which the resource can be distributed.
@@ -74,7 +76,7 @@
 @XmlType(name = "MD_Medium_Type", propOrder = {
     "identifier",           // New in ISO 19115-3
     "name",
-    "legacyName",           // From ISO 19115:2003
+    "newName",              // From ISO 19115:2014
     "density",
     "densities",
     "densityUnits",
@@ -87,13 +89,13 @@
     /**
      * Serial number for inter-operability with different versions.
      */
-    private static final long serialVersionUID = -460355952171320089L;
+    private static final long serialVersionUID = 2657393801067168091L;
 
     /**
      * Name of the medium on which the resource can be received.
      */
     @SuppressWarnings("serial")
-    private Citation name;
+    private MediumName name;
 
     /**
      * Density at which the data is recorded.
@@ -186,21 +188,30 @@
     /**
      * Returns the name of the medium on which the resource can be received.
      *
+     * <div class="warning"><b>Upcoming API change</b><br>
+     * {@link MediumName} may be replaced by {@link Citation} in GeoAPI 4.0.
+     * </div>
+     *
      * @return name of the medium, or {@code null}.
+     *
+     * @see <a href="https://issues.apache.org/jira/browse/SIS-389">SIS-389</a>
      */
     @Override
-    @XmlElement(name = "name")
-    @XmlJavaTypeAdapter(CI_Citation.Since2014.class)
-    public Citation getName() {
-        return name;
+    @XmlElement(name = "name", namespace = LegacyNamespaces.GMD)
+    public MediumName getName() {
+        return FilterByVersion.LEGACY_METADATA.accept() ? name : null;
     }
 
     /**
      * Sets the name of the medium on which the resource can be received.
      *
+     * <div class="warning"><b>Upcoming API change</b><br>
+     * {@link MediumName} may be replaced by {@link Citation} in GeoAPI 4.0.
+     * </div>
+     *
      * @param  newValue  the new name.
      */
-    public void setName(final Citation newValue) {
+    public void setName(final MediumName newValue) {
         checkWritePermission(name);
         name = newValue;
     }
@@ -414,15 +425,21 @@
     /**
      * Returns the medium name as a code list.
      */
-    @XmlElement(name = "name", namespace = LegacyNamespaces.GMD)
-    private MediumName getLegacyName() {
-        return FilterByVersion.LEGACY_METADATA.accept() ? MediumName.castOrWrap(name) : null;
+    @XmlElement(name = "name")
+    @XmlJavaTypeAdapter(CI_Citation.Since2014.class)
+    private Citation getNewName() {
+        return (name != null) ? new DefaultCitation(name.name()) : null;
     }
 
     /**
      * Sets the name of the medium on which the resource can be received.
      */
-    private void setLegacyName(final MediumName newValue) {
-        name = newValue;
+    private void setNewName(final Citation newValue) {
+        if (newValue != null) {
+            final InternationalString title = newValue.getTitle();
+            if (title != null) {
+                name = CodeLists.forCodeName(MediumName.class, title.toString());
+            }
+        }
     }
 }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/distribution/package-info.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/distribution/package-info.java
index 07becb2..ec53472 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/distribution/package-info.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/distribution/package-info.java
@@ -38,6 +38,7 @@
  * {@code  ├─} {@linkplain org.apache.sis.metadata.iso.distribution.DefaultDigitalTransferOptions Digital transfer options}<br>
  * {@code  └─} {@linkplain org.apache.sis.metadata.iso.distribution.DefaultDataFile               Data file}<br>
  * {@linkplain org.opengis.util.CodeList Code list}<br>
+ * {@code  ├─} {@linkplain org.opengis.metadata.distribution.MediumName   Medium name}<br>
  * {@code  └─} {@linkplain org.opengis.metadata.distribution.MediumFormat Medium format}<br>
  * </td><td class="sep" style="width: 50%; white-space: nowrap">
  *                     {@linkplain org.apache.sis.metadata.iso.distribution.DefaultDistribution           Distribution}<br>
@@ -46,6 +47,7 @@
  * {@code  │   └─}     {@linkplain org.apache.sis.metadata.iso.distribution.DefaultStandardOrderProcess   Standard order process}<br>
  * {@code  └─}         {@linkplain org.apache.sis.metadata.iso.distribution.DefaultDigitalTransferOptions Digital transfer options}<br>
  * {@code      └─}     {@linkplain org.apache.sis.metadata.iso.distribution.DefaultMedium                 Medium}<br>
+ * {@code          ├─} {@linkplain org.opengis.metadata.distribution.MediumName                           Medium name} «code list»<br>
  * {@code          └─} {@linkplain org.opengis.metadata.distribution.MediumFormat                         Medium format} «code list»<br>
  *                     {@linkplain org.apache.sis.metadata.iso.distribution.DefaultDataFile               Data file}<br>
  * </td></tr></table>
@@ -94,6 +96,7 @@
     @XmlJavaTypeAdapter(MD_Medium.class),
     @XmlJavaTypeAdapter(MD_MediumFormatCode.class),
     @XmlJavaTypeAdapter(MD_MediumNameCode.class),
+    @XmlJavaTypeAdapter(CI_ResponsibleParty.class),
     @XmlJavaTypeAdapter(MD_StandardOrderProcess.class),
 
     // Java types, primitive types and basic OGC types handling
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 f6d67b7..b6c5e65 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
@@ -49,12 +49,12 @@
 import org.apache.sis.xml.privy.LegacyNamespaces;
 import org.apache.sis.util.iso.Types;
 
+// 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 geoapi-4.0 branch:
-import org.opengis.metadata.citation.Responsibility;
-
 
 /**
  * Basic information required to uniquely identify a resource or resources.
@@ -151,7 +151,7 @@
      * Recognition of those who contributed to the resource(s).
      */
     @SuppressWarnings("serial")
-    private Collection<InternationalString> credits;
+    private Collection<String> credits;
 
     /**
      * Status of the resource(s).
@@ -164,7 +164,7 @@
      * associated with the resource(s).
      */
     @SuppressWarnings("serial")
-    private Collection<Responsibility> pointOfContacts;
+    private Collection<ResponsibleParty> pointOfContacts;
 
     /**
      * Methods used to spatially represent geographic information.
@@ -284,9 +284,9 @@
             citation                   = object.getCitation();
             abstracts                  = object.getAbstract();
             purpose                    = object.getPurpose();
-            credits                    = copyCollection(object.getCredits(), InternationalString.class);
+            credits                    = copyCollection(object.getCredits(), String.class);
             status                     = copyCollection(object.getStatus(), Progress.class);
-            pointOfContacts            = copyCollection(object.getPointOfContacts(), Responsibility.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);
@@ -407,21 +407,29 @@
     /**
      * Returns the recognition of those who contributed to the resource(s).
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type may be changed to the {@code InternationalString} interface in GeoAPI 4.0.
+     * </div>
+     *
      * @return recognition of those who contributed to the resource(s).
      */
     @Override
     @XmlElement(name = "credit")
-    public Collection<InternationalString> getCredits() {
-        return credits = nonNullCollection(credits, InternationalString.class);
+    public Collection<String> getCredits() {
+        return credits = nonNullCollection(credits, String.class);
     }
 
     /**
      * Sets the recognition of those who contributed to the resource(s).
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type may be changed to the {@code InternationalString} interface in GeoAPI 4.0.
+     * </div>
+     *
      * @param  newValues  the new credits.
      */
-    public void setCredits(final Collection<? extends InternationalString> newValues) {
-        credits = writeCollection(newValues, credits, InternationalString.class);
+    public void setCredits(final Collection<? extends String> newValues) {
+        credits = writeCollection(newValues, credits, String.class);
     }
 
     /**
@@ -448,23 +456,33 @@
      * Returns the identification of, and means of communication with, person(s) and organizations(s)
      * associated with the resource(s).
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * As of ISO 19115:2014, {@code ResponsibleParty} is replaced by the {@code Responsibility} parent interface.
+     * This change may be applied in GeoAPI 4.0.
+     * </div>
+     *
      * @return means of communication with person(s) and organizations(s) associated with the resource(s).
      *
      * @see org.apache.sis.metadata.iso.DefaultMetadata#getContacts()
      */
     @Override
     @XmlElement(name = "pointOfContact")
-    public Collection<Responsibility> getPointOfContacts() {
-        return pointOfContacts = nonNullCollection(pointOfContacts, Responsibility.class);
+    public Collection<ResponsibleParty> getPointOfContacts() {
+        return pointOfContacts = nonNullCollection(pointOfContacts, ResponsibleParty.class);
     }
 
     /**
      * Sets the means of communication with persons(s) and organizations(s) associated with the resource(s).
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * As of ISO 19115:2014, {@code ResponsibleParty} is replaced by the {@code Responsibility} parent interface.
+     * This change may be applied in GeoAPI 4.0.
+     * </div>
+     *
      * @param  newValues  the new points of contacts.
      */
-    public void setPointOfContacts(final Collection<? extends Responsibility> newValues) {
-        pointOfContacts = writeCollection(newValues, pointOfContacts, Responsibility.class);
+    public void setPointOfContacts(final Collection<? extends ResponsibleParty> newValues) {
+        pointOfContacts = writeCollection(newValues, pointOfContacts, ResponsibleParty.class);
     }
 
     /**
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 15b7bf2..d4d70d2 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
@@ -35,6 +35,10 @@
 import org.apache.sis.xml.privy.LegacyNamespaces;
 import org.apache.sis.metadata.internal.Dependencies;
 
+// Specific to the main and geoapi-3.1 branches:
+import java.util.stream.Collectors;
+import org.opengis.metadata.identification.CharacterSet;
+
 
 /**
  * Information required to identify a dataset.
@@ -73,7 +77,7 @@
  */
 @XmlType(name = "MD_DataIdentification_Type", propOrder = {
     "languages",                // Legacy ISO 19115:2003
-    "characterSets",            // Legacy ISO 19115:2003
+    "characterSet",             // Legacy ISO 19115:2003
     "defaultLocale",            // New in ISO 19115:2014
     "otherLocales",             // New in ISO 19115:2014
     "environmentDescription",
@@ -247,6 +251,10 @@
     /**
      * Returns the character coding standard used for the dataset.
      *
+     * <div class="warning"><b>Upcoming API change — JDK integration</b><br>
+     * The element type may change to the {@link Charset} class in GeoAPI 4.0.
+     * </div>
+     *
      * @return character coding standard(s) used.
      *
      * @deprecated Replaced by {@code getLocalesAndCharsets().values()}.
@@ -254,22 +262,30 @@
     @Override
     @Deprecated(since="1.0")
     @Dependencies("getLocalesAndCharsets")
-    @XmlElement(name = "characterSet", namespace = LegacyNamespaces.GMD)
-    public Collection<Charset> getCharacterSets() {
-        return FilterByVersion.LEGACY_METADATA.accept() ? LocaleAndCharset.getCharacterSets(getLocalesAndCharsets()) : null;
+    // @XmlElement at the end of this class.
+    public Collection<CharacterSet> getCharacterSets() {
+        return getLocalesAndCharsets().values().stream().map(CharacterSet::fromCharset).collect(Collectors.toSet());
     }
 
     /**
      * Sets the character coding standard used for the dataset.
      *
+     * <div class="warning"><b>Upcoming API change — JDK integration</b><br>
+     * The element type may change to the {@link Charset} class in GeoAPI 4.0.
+     * </div>
+     *
      * @param  newValues  the new character sets.
      *
      * @deprecated Replaced by putting values in {@link #getLocalesAndCharsets()} map.
      */
     @Deprecated(since="1.0")
-    public void setCharacterSets(final Collection<? extends Charset> newValues) {
+    public void setCharacterSets(final Collection<? extends CharacterSet> newValues) {
         // TODO: delete after SIS 1.0 release (method not needed by JAXB).
-        setLocalesAndCharsets(LocaleAndCharset.setCharacterSets(getLocalesAndCharsets(), newValues));
+        Collection<Charset> c = null;
+        if (newValues != null) {
+            c = newValues.stream().map(CharacterSet::toCharset).collect(Collectors.toSet());
+        }
+        setLocalesAndCharsets(LocaleAndCharset.setCharacterSets(getLocalesAndCharsets(), c));
     }
 
     /**
@@ -354,4 +370,14 @@
     private Collection<PT_Locale> getOtherLocales() {
         return FilterByVersion.CURRENT_METADATA.accept() ? OtherLocales.filter(getLocalesAndCharsets()) : null;
     }
+
+    /**
+     * Returns the character coding for the metadata set (used in legacy ISO 19157 format).
+     *
+     * @see #getCharacterSets()
+     */
+    @XmlElement(name = "characterSet", namespace = LegacyNamespaces.GMD)
+    private Collection<Charset> getCharacterSet() {
+        return FilterByVersion.LEGACY_METADATA.accept() ? LocaleAndCharset.getCharacterSets(getLocalesAndCharsets()) : 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 919e5fc..1ecbfbe 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
@@ -31,8 +31,8 @@
 import static org.apache.sis.metadata.privy.ImplementationHelper.toDate;
 import static org.apache.sis.metadata.privy.ImplementationHelper.toMilliseconds;
 
-// Specific to the geoapi-4.0 branch:
-import org.opengis.metadata.citation.Responsibility;
+// Specific to the main and geoapi-3.1 branches:
+import org.opengis.metadata.citation.ResponsibleParty;
 
 
 /**
@@ -105,7 +105,7 @@
      * Identification of and means of communicating with person(s) and organization(s) using the resource(s).
      */
     @SuppressWarnings("serial")
-    private Collection<Responsibility> userContactInfo;
+    private Collection<ResponsibleParty> userContactInfo;
 
     /**
      * Responses to the user-determined limitations.
@@ -139,10 +139,10 @@
      * @param userContactInfo  means of communicating with person(s) and organization(s), or {@code null} if none.
      */
     public DefaultUsage(final CharSequence specificUsage,
-                        final Responsibility userContactInfo)
+                        final ResponsibleParty userContactInfo)
     {
         this.specificUsage   = Types.toInternationalString(specificUsage);
-        this.userContactInfo = singleton(userContactInfo, Responsibility.class);
+        this.userContactInfo = singleton(userContactInfo, ResponsibleParty.class);
     }
 
     /**
@@ -160,7 +160,7 @@
             specificUsage             = object.getSpecificUsage();
             usageDate                 = toMilliseconds(object.getUsageDate());
             userDeterminedLimitations = object.getUserDeterminedLimitations();
-            userContactInfo           = copyCollection(object.getUserContactInfo(), Responsibility.class);
+            userContactInfo           = copyCollection(object.getUserContactInfo(), ResponsibleParty.class);
             responses                 = copyCollection(object.getResponses(), InternationalString.class);
             additionalDocumentation   = copyCollection(object.getAdditionalDocumentation(), Citation.class);
             identifiedIssues          = copyCollection(object.getIdentifiedIssues(), Citation.class);
@@ -258,21 +258,31 @@
     /**
      * Returns identification of and means of communicating with person(s) and organization(s) using the resource(s).
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * As of ISO 19115:2014, {@code ResponsibleParty} is replaced by the {@code Responsibility} parent interface.
+     * This change may be applied in GeoAPI 4.0.
+     * </div>
+     *
      * @return means of communicating with person(s) and organization(s) using the resource(s).
      */
     @Override
     @XmlElement(name = "userContactInfo")
-    public Collection<Responsibility> getUserContactInfo() {
-        return userContactInfo = nonNullCollection(userContactInfo, Responsibility.class);
+    public Collection<ResponsibleParty> getUserContactInfo() {
+        return userContactInfo = nonNullCollection(userContactInfo, ResponsibleParty.class);
     }
 
     /**
      * Sets identification of and means of communicating with person(s) and organization(s) using the resource(s).
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * As of ISO 19115:2014, {@code ResponsibleParty} is replaced by the {@code Responsibility} parent interface.
+     * This change may be applied in GeoAPI 4.0.
+     * </div>
+     *
      * @param  newValues  the new user contact info.
      */
-    public void setUserContactInfo(final Collection<? extends Responsibility> newValues) {
-        userContactInfo = writeCollection(newValues, userContactInfo, Responsibility.class);
+    public void setUserContactInfo(final Collection<? extends ResponsibleParty> newValues) {
+        userContactInfo = writeCollection(newValues, userContactInfo, ResponsibleParty.class);
     }
 
     /**
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 8aa8ff4..6db83b5 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
@@ -114,7 +114,7 @@
 @XmlJavaTypeAdapters({
     @XmlJavaTypeAdapter(CI_Citation.class),
     @XmlJavaTypeAdapter(CI_OnlineResource.class),
-    @XmlJavaTypeAdapter(CI_Responsibility.class),
+    @XmlJavaTypeAdapter(CI_ResponsibleParty.class),
     @XmlJavaTypeAdapter(DCPList.class),
     @XmlJavaTypeAdapter(EX_Extent.class),
     @XmlJavaTypeAdapter(GO_DateTime.class),
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/legacy/MediumName.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/legacy/MediumName.java
deleted file mode 100644
index 6901065..0000000
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/legacy/MediumName.java
+++ /dev/null
@@ -1,170 +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.iso.legacy;
-
-import org.opengis.annotation.UML;
-import org.opengis.metadata.citation.Citation;
-import org.opengis.util.CodeList;
-import org.opengis.util.InternationalString;
-import static org.opengis.annotation.Obligation.*;
-import static org.opengis.annotation.Specification.*;
-import org.apache.sis.util.iso.Types;
-
-
-/**
- * Name of the medium as defined in legacy ISO 19115:2003.
- * In more recent specification, this code list has been replaced by a citation.
- *
- * @author  Martin Desruisseaux (Geomatys)
- *
- * @see <a href="https://issues.apache.org/jira/browse/SIS-389">SIS-389</a>
- * @see <a href="https://github.com/opengeospatial/geoapi/issues/14">GeoAPI issue #14</a>
- */
-@UML(identifier="MD_MediumNameCode", specification=ISO_19115, version=2003)
-public final class MediumName extends CodeList<MediumName> implements Citation {
-    /** Serial number for compatibility with different versions. */
-    private static final long serialVersionUID = 7157038832444373933L;
-
-    /*
-     * We need to construct values with `valueOf(String)` instead of the constructor
-     * because this package is not exported to GeoAPI. See `CodeList` class javadoc.
-     */
-
-    /** Read-only optical disk. */
-    @UML(identifier="cdRom", obligation=CONDITIONAL, specification=ISO_19115)
-    public static final MediumName CD_ROM = valueOf("CD_ROM");
-
-    /** Digital versatile disk. */
-    @UML(identifier="dvd", obligation=CONDITIONAL, specification=ISO_19115)
-    public static final MediumName DVD = valueOf("DVD");
-
-    /** Digital versatile disk digital versatile disk, read only. */
-    @UML(identifier="dvdRom", obligation=CONDITIONAL, specification=ISO_19115)
-    public static final MediumName DVD_ROM = valueOf("DVD_ROM");
-
-    /** 3½ inch magnetic disk. */
-    @UML(identifier="3halfInchFloppy", obligation=CONDITIONAL, specification=ISO_19115)
-    public static final MediumName FLOPPY_3_HALF_INCH = valueOf("FLOPPY_3_HALF_INCH");
-
-    /** 5¼ inch magnetic disk. */
-    @UML(identifier="5quarterInchFloppy", obligation=CONDITIONAL, specification=ISO_19115)
-    public static final MediumName FLOPPY_5_QUARTER_INCH = valueOf("FLOPPY_5_QUARTER_INCH");
-
-    /** 7 track magnetic tape. */
-    @UML(identifier="7trackTape", obligation=CONDITIONAL, specification=ISO_19115)
-    public static final MediumName TAPE_7_TRACK = valueOf("TAPE_7_TRACK");
-
-    /** 9 track magnetic tape. */
-    @UML(identifier="9trackTape", obligation=CONDITIONAL, specification=ISO_19115)
-    public static final MediumName TAPE_9_TRACK = valueOf("TAPE_9_TRACK");
-
-    /** 3480 cartridge tape drive. */
-    @UML(identifier="3480Cartridge", obligation=CONDITIONAL, specification=ISO_19115)
-    public static final MediumName CARTRIDGE_3480 = valueOf("CARTRIDGE_3480");
-
-    /** 3490 cartridge tape drive. */
-    @UML(identifier="3490Cartridge", obligation=CONDITIONAL, specification=ISO_19115)
-    public static final MediumName CARTRIDGE_3490 = valueOf("CARTRIDGE_3490");
-
-    /** 3580 cartridge tape drive. */
-    @UML(identifier="3580Cartridge", obligation=CONDITIONAL, specification=ISO_19115)
-    public static final MediumName CARTRIDGE_3580 = valueOf("CARTRIDGE_3580");
-
-    /** 4 millimetre magnetic tape. */
-    @UML(identifier="4mmCartridgeTape", obligation=CONDITIONAL, specification=ISO_19115)
-    public static final MediumName CARTRIDGE_TAPE_4mm = valueOf("CARTRIDGE_TAPE_4mm");
-
-    /** 8 millimetre magnetic tape. */
-    @UML(identifier="8mmCartridgeTape", obligation=CONDITIONAL, specification=ISO_19115)
-    public static final MediumName CARTRIDGE_TAPE_8mm = valueOf("CARTRIDGE_TAPE_8mm");
-
-    /** ¼ inch magnetic tape. */
-    @UML(identifier="1quarterInchCartridgeTape", obligation=CONDITIONAL, specification=ISO_19115)
-    public static final MediumName CARTRIDGE_TAPE_1_QUARTER_INCH = valueOf("CARTRIDGE_TAPE_1_QUARTER_INCH");
-
-    /** Half inch cartridge streaming tape drive. */
-    @UML(identifier="digitalLinearTape", obligation=CONDITIONAL, specification=ISO_19115)
-    public static final MediumName DIGITAL_LINEAR_TAPE = valueOf("DIGITAL_LINEAR_TAPE");
-
-    /** Direct computer linkage. */
-    @UML(identifier="onLine", obligation=CONDITIONAL, specification=ISO_19115)
-    public static final MediumName ON_LINE = valueOf("ON_LINE");
-
-    /** Linkage through a satellite communication system. */
-    @UML(identifier="satellite", obligation=CONDITIONAL, specification=ISO_19115)
-    public static final MediumName SATELLITE = valueOf("SATELLITE");
-
-    /** Communication through a telephone network. */
-    @UML(identifier="telephoneLink", obligation=CONDITIONAL, specification=ISO_19115)
-    public static final MediumName TELEPHONE_LINK = valueOf("TELEPHONE_LINK");
-
-    /** Pamphlet or leaflet giving descriptive information. */
-    @UML(identifier="hardcopy", obligation=CONDITIONAL, specification=ISO_19115)
-    public static final MediumName HARDCOPY = valueOf("HARDCOPY");
-
-    /** Constructs an element of the given name. */
-    private MediumName(final String name) {
-        super(name);
-    }
-
-    /**
-     * Returns the list of {@code MediumName}s.
-     *
-     * @return the list of codes declared in the current JVM.
-     */
-    @Override
-    public MediumName[] family() {
-        return values(MediumName.class);
-    }
-
-    /**
-     * Returns the medium name that matches the given string, or 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, or {@code null}.
-     */
-    public static MediumName valueOf(final String code) {
-        return valueOf(MediumName.class, code, MediumName::new).get();
-    }
-
-    /**
-     * Returns the given citation as a medium name code, or {@code null} if none.
-     *
-     * @param  citation  the medium name to return as a citation, or {@code null}.
-     * @return the code as a citation, or {@code null}.
-     */
-    public static MediumName castOrWrap(final Citation citation) {
-        if (citation instanceof MediumName) {
-            return (MediumName) citation;
-        }
-        if (citation != null) {
-            final InternationalString title = citation.getTitle();
-            if (title != null) {
-                return Types.forCodeName(MediumName.class, title.toString(), null);
-            }
-        }
-        return null;
-    }
-
-    /**
-     * {@link Citation} methods provided for transition from legacy code list to new citation type.
-     */
-    @Override
-    public InternationalString getTitle() {
-        return Types.toInternationalString(name());
-    }
-}
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 25444ad..4faa4c3 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
@@ -23,10 +23,10 @@
 import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
 import org.opengis.util.InternationalString;
 import org.opengis.metadata.citation.Citation;
-import org.opengis.metadata.maintenance.ScopeCode;
 import org.opengis.metadata.lineage.Source;
 import org.opengis.metadata.lineage.Lineage;
 import org.opengis.metadata.lineage.ProcessStep;
+import org.opengis.metadata.maintenance.ScopeCode;
 import org.apache.sis.metadata.iso.ISOMetadata;
 import org.apache.sis.metadata.iso.maintenance.DefaultScope;
 import org.apache.sis.xml.bind.FilterByVersion;
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 05df948..a91ec94 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
@@ -39,12 +39,12 @@
 import org.apache.sis.xml.privy.LegacyNamespaces;
 import org.apache.sis.pending.temporal.TemporalUtilities;
 
+// 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 geoapi-4.0 branch:
-import org.opengis.metadata.citation.Responsibility;
-
 
 /**
  * Information about an event or transformation in the life of a resource.
@@ -116,7 +116,7 @@
      * organization(s) associated with the process step.
      */
     @SuppressWarnings("serial")
-    private Collection<Responsibility> processors;
+    private Collection<ResponsibleParty> processors;
 
     /**
      * Process step documentation.
@@ -186,8 +186,8 @@
         if (object != null) {
             description           = object.getDescription();
             rationale             = object.getRationale();
-            stepDateTime          = object.getStepDateTime();
-            processors            = copyCollection(object.getProcessors(), Responsibility.class);
+            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();
@@ -327,22 +327,32 @@
      * Returns the identification of, and means of communication with, person(s) and
      * organization(s) associated with the process step.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * As of ISO 19115:2014, {@code ResponsibleParty} is replaced by the {@code Responsibility} parent interface.
+     * This change may be applied in GeoAPI 4.0.
+     * </div>
+     *
      * @return means of communication with person(s) and organization(s) associated with the process step.
      */
     @Override
     @XmlElement(name = "processor")
-    public Collection<Responsibility> getProcessors() {
-        return processors = nonNullCollection(processors, Responsibility.class);
+    public Collection<ResponsibleParty> getProcessors() {
+        return processors = nonNullCollection(processors, ResponsibleParty.class);
     }
 
     /**
      * Identification of, and means of communication with, person(s) and
      * organization(s) associated with the process step.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * As of ISO 19115:2014, {@code ResponsibleParty} is replaced by the {@code Responsibility} parent interface.
+     * This change may be applied in GeoAPI 4.0.
+     * </div>
+     *
      * @param  newValues  the new processors.
      */
-    public void setProcessors(final Collection<? extends Responsibility> newValues) {
-        processors = writeCollection(newValues, processors, Responsibility.class);
+    public void setProcessors(final Collection<? extends ResponsibleParty> newValues) {
+        processors = writeCollection(newValues, processors, ResponsibleParty.class);
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/lineage/package-info.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/lineage/package-info.java
index e1ce766..1209509 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/lineage/package-info.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/lineage/package-info.java
@@ -79,7 +79,7 @@
 @XmlAccessorType(XmlAccessType.NONE)
 @XmlJavaTypeAdapters({
     @XmlJavaTypeAdapter(CI_Citation.class),
-    @XmlJavaTypeAdapter(CI_Responsibility.class),
+    @XmlJavaTypeAdapter(CI_ResponsibleParty.class),
     @XmlJavaTypeAdapter(EX_Extent.class),
     @XmlJavaTypeAdapter(GO_Real.class),
     @XmlJavaTypeAdapter(GO_DateTime.class),
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
new file mode 100644
index 0000000..d24789d
--- /dev/null
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/maintenance/AttributeTypeAdapter.java
@@ -0,0 +1,49 @@
+/*
+ * 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.iso.maintenance;
+
+import jakarta.xml.bind.annotation.adapters.XmlAdapter;
+import org.opengis.feature.type.AttributeType;
+import org.apache.sis.xml.bind.gco.GO_CharacterString;
+
+
+/**
+ * For (un)marshalling deprecated {@link AttributeType} as a character string,
+ * as expected by ISO 19115-3:2016. This is a temporary bridge to be removed
+ * after the GeoAPI interfaces has been upgraded to ISO 19115-1:2014 model.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ */
+final class AttributeTypeAdapter extends XmlAdapter<GO_CharacterString, AttributeType> {
+    /**
+     * Wrap the given value from {@link DefaultScopeDescription} to the elements
+     * defined by ISO 19115-3:2016 schema.
+     */
+    @Override
+    public AttributeType unmarshal(GO_CharacterString value) {
+        return new LegacyFeatureType(LegacyFeatureType.ADAPTER.unmarshal(value));
+    }
+
+    /**
+     * Unwrap the elements defined by ISO 19115-3:2016 schema to the value used by
+     * {@link DefaultScopeDescription}.
+     */
+    @Override
+    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 226e4e6..889d8a7 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
@@ -39,13 +39,13 @@
 import org.apache.sis.util.privy.CollectionsExt;
 import static org.apache.sis.metadata.privy.ImplementationHelper.valueIfDefined;
 
+// Specific to the main and geoapi-3.1 branches:
+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 geoapi-4.0 branch:
-import java.time.temporal.TemporalAmount;
-import org.opengis.metadata.citation.Responsibility;
-
 
 /**
  * Information about the scope and frequency of updating.
@@ -106,7 +106,7 @@
      * Maintenance period other than those defined, in milliseconds.
      */
     @SuppressWarnings("serial")
-    private TemporalAmount userDefinedMaintenanceFrequency;
+    private PeriodDuration userDefinedMaintenanceFrequency;
 
     /**
      * Type of resource and / or extent to which the maintenance information applies.
@@ -125,7 +125,7 @@
      * with responsibility for maintaining the resource.
      */
     @SuppressWarnings("serial")
-    private Collection<Responsibility> contacts;
+    private Collection<ResponsibleParty> contacts;
 
     /**
      * Creates a an initially empty maintenance information.
@@ -161,7 +161,7 @@
             userDefinedMaintenanceFrequency = object.getUserDefinedMaintenanceFrequency();
             maintenanceScopes               = copyCollection(object.getMaintenanceScopes(), Scope.class);
             maintenanceNotes                = copyCollection(object.getMaintenanceNotes(), InternationalString.class);
-            contacts                        = copyCollection(object.getContacts(), Responsibility.class);
+            contacts                        = copyCollection(object.getContacts(), ResponsibleParty.class);
         }
     }
 
@@ -305,20 +305,30 @@
     /**
      * Returns the maintenance period other than those defined.
      *
+     * <div class="warning"><b>Upcoming API change</b><br>
+     * The return type may be changed to {@link java.time.temporal.TemporalAmount} in Apache SIS 2.0.
+     * This change depend on a corresponding change in GeoAPI interfaces.
+     * </div>
+     *
      * @return the maintenance period, or {@code null}.
      */
     @Override
     @XmlElement(name = "userDefinedMaintenanceFrequency")
-    public TemporalAmount getUserDefinedMaintenanceFrequency() {
+    public PeriodDuration getUserDefinedMaintenanceFrequency() {
         return userDefinedMaintenanceFrequency;
     }
 
     /**
      * Sets the maintenance period other than those defined.
      *
+     * <div class="warning"><b>Upcoming API change</b><br>
+     * The parameter type may be changed to {@link java.time.temporal.TemporalAmount} in Apache SIS 2.0.
+     * This change depend on a corresponding change in GeoAPI interfaces.
+     * </div>
+     *
      * @param  newValue  the new user defined maintenance frequency.
      */
-    public void setUserDefinedMaintenanceFrequency(final TemporalAmount newValue) {
+    public void setUserDefinedMaintenanceFrequency(final PeriodDuration newValue) {
         checkWritePermission(userDefinedMaintenanceFrequency);
         userDefinedMaintenanceFrequency = newValue;
     }
@@ -478,24 +488,34 @@
      * Returns identification of, and means of communicating with,
      * person(s) and organization(s) with responsibility for maintaining the resource.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * As of ISO 19115:2014, {@code ResponsibleParty} is replaced by the {@code Responsibility} parent interface.
+     * This change may be applied in GeoAPI 4.0.
+     * </div>
+     *
      * @return means of communicating with person(s) and organization(s) with responsibility
      *         for maintaining the resource.
      */
     @Override
     @XmlElement(name = "contact")
-    public Collection<Responsibility> getContacts() {
-        return contacts = nonNullCollection(contacts, Responsibility.class);
+    public Collection<ResponsibleParty> getContacts() {
+        return contacts = nonNullCollection(contacts, ResponsibleParty.class);
     }
 
     /**
      * Sets identification of, and means of communicating with,
      * person(s) and organization(s) with responsibility for maintaining the resource.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * As of ISO 19115:2014, {@code ResponsibleParty} is replaced by the {@code Responsibility} parent interface.
+     * This change may be applied in GeoAPI 4.0.
+     * </div>
+     *
      * @param  newValues  the new identification of person(s) and organization(s)
      *                    with responsibility for maintaining the resource.
      */
-    public void setContacts(final Collection<? extends Responsibility> newValues) {
-        contacts = writeCollection(newValues, contacts, Responsibility.class);
+    public void setContacts(final Collection<? extends ResponsibleParty> newValues) {
+        contacts = writeCollection(newValues, contacts, ResponsibleParty.class);
     }
 
 
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 1ed6037..9e61cd6 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
@@ -33,8 +33,9 @@
 import org.apache.sis.util.resources.Messages;
 import static org.apache.sis.util.collection.Containers.isNullOrEmpty;
 
-// Specific to the geoapi-4.0 branch:
-import org.apache.sis.util.iso.Types;
+// Specific to the main and geoapi-3.1 branches:
+import org.opengis.feature.type.AttributeType;
+import org.opengis.feature.type.FeatureType;
 
 
 /**
@@ -117,12 +118,12 @@
      * The value, as one of the following types:
      *
      * <ul>
-     *   <li>{@code Set<CharSequence>}   for the {@code features} property</li>
-     *   <li>{@code Set<CharSequence>}   for the {@code attributes} property</li>
-     *   <li>{@code Set<CharSequence>}   for the {@code featureInstances} property</li>
-     *   <li>{@code Set<CharSequence>}   for the {@code attributeInstances} property</li>
-     *   <li>{@code String}              for the {@code dataset} property</li>
-     *   <li>{@code InternationalString} for the {@code other} property</li>
+     *   <li>{@code Set<FeatureType>}   for the {@code features} property</li>
+     *   <li>{@code Set<AttributeType>} for the {@code attributes} property</li>
+     *   <li>{@code Set<FeatureType>}   for the {@code featureInstances} property</li>
+     *   <li>{@code Set<AttributeType>} for the {@code attributeInstances} property</li>
+     *   <li>{@code String} for the {@code dataset} property</li>
+     *   <li>{@code String} for the {@code other} property</li>
      * </ul>
      */
     @SuppressWarnings("serial")         // Not statically typed as Serializable.
@@ -152,27 +153,36 @@
      *
      * @see #castOrCopy(ScopeDescription)
      */
+    @SuppressWarnings("unchecked")
     public DefaultScopeDescription(final ScopeDescription object) {
         super(object);
         if (object != null) {
             for (byte i=DATASET; i<=OTHER; i++) {
-                Collection<? extends CharSequence> props = null;
-                Object value = null;
+                Object candidate;
                 switch (i) {
-                    case DATASET:             value = object.getDataset();            break;
-                    case FEATURES:            props = object.getFeatures();           break;
-                    case ATTRIBUTES:          props = object.getAttributes();         break;
-                    case FEATURE_INSTANCES:   props = object.getFeatureInstances();   break;
-                    case ATTRIBUTE_INSTANCES: props = object.getAttributeInstances(); break;
-                    case OTHER:               value = object.getOther();              break;
+                    case DATASET:             candidate = object.getDataset();            break;
+                    case FEATURES:            candidate = object.getFeatures();           break;
+                    case ATTRIBUTES:          candidate = object.getAttributes();         break;
+                    case FEATURE_INSTANCES:   candidate = object.getFeatureInstances();   break;
+                    case ATTRIBUTE_INSTANCES: candidate = object.getAttributeInstances(); break;
+                    case OTHER:               candidate = object.getOther();              break;
                     default: throw new AssertionError(i);
                 }
-                if (props != null) {
-                    value = copySet(props, CharSequence.class);
-                }
-                if (value != null) {
-                    this.value = value;
-                    this.property = i;
+                if (candidate != null) {
+                    switch (i) {
+                        case ATTRIBUTES:
+                        case ATTRIBUTE_INSTANCES: {
+                            candidate = copySet((Collection<AttributeType>) candidate, AttributeType.class);
+                            break;
+                        }
+                        case FEATURES:
+                        case FEATURE_INSTANCES: {
+                            candidate = copySet((Collection<FeatureType>) candidate, FeatureType.class);
+                            break;
+                        }
+                    }
+                    value = candidate;
+                    property = i;
                     break;
                 }
             }
@@ -205,34 +215,36 @@
     }
 
     /**
-     * Returns the given value casted to a {@code Set<CharSequence>}.
+     * Returns the given value casted to a {@code Set} of elements of the given type.
+     * It is caller responsibility to ensure that the cast is valid, as element type
+     * is verified only when assertions are enabled.
      */
     @SuppressWarnings("unchecked")
-    private static Set<CharSequence> cast(final Object value) {
+    private static <E> Set<E> cast(final Object value, final Class<E> type) {
         assert !(value instanceof CheckedContainer<?>) ||
-                ((CheckedContainer<?>) value).getElementType() == CharSequence.class;
-        return (Set<CharSequence>) value;
+                ((CheckedContainer<?>) value).getElementType() == type;
+        return (Set<E>) value;
     }
 
     /**
      * Returns the set of properties identified by the {@code code} argument,
      * or an unmodifiable empty set if another value is defined.
      */
-    private Set<CharSequence> getProperty(final byte code) {
+    private <E> Set<E> getProperty(final Class<E> type, final byte code) {
         final Object value = this.value;
         if (value != null) {
             if (property == code) {
-                return cast(value);
+                return cast(value, type);
             } else if (!(value instanceof Set) || !((Set<?>) value).isEmpty()) {
                 return Semaphores.query(Semaphores.NULL_COLLECTION)
-                       ? null : new ExcludedSet<>(NAMES[code-1], NAMES[property-1]);
+                       ? null : new ExcludedSet<E>(NAMES[code-1], NAMES[property-1]);
             }
         }
         /*
          * Unconditionally create a new set, because the
          * user may hold a reference to the previous one.
          */
-        final Set<CharSequence> c = nonNullSet(null, CharSequence.class);
+        final Set<E> c = nonNullSet(null, type);
         property = code;
         this.value = c;
         return c;
@@ -245,17 +257,17 @@
      * @param newValue  the value to set.
      * @param code      the property which is going to be set.
      */
-    private void setProperty(final Set<? extends CharSequence> newValue, final byte code) {
-        Set<CharSequence> c = null;
+    private <E> void setProperty(final Set<? extends E> newValue, final Class<E> type, final byte code) {
+        Set<E> c = null;
         if (property == code) {
-            c = cast(value);
+            c = cast(value, type);
         } else if (isNullOrEmpty(newValue)) {
             return;
         } else {
             warningOnOverwrite(code);
             property = code;
         }
-        value = writeSet(newValue, c, CharSequence.class);
+        value = writeSet(newValue, c, type);
     }
 
     /**
@@ -348,12 +360,16 @@
      * This method returns a modifiable collection only if no other property is set.
      * Otherwise, this method returns an unmodifiable empty collection.
      *
+     * <div class="warning"><b>Upcoming API change:</b>
+     * The type of this property may be changed to {@code Set<CharSequence>} for ISO 19115:2014 conformance.
+     * See <a href="http://jira.codehaus.org/browse/GEO-238">GEO-238</a> for more information.</div>
+     *
      * @return feature types to which the information applies.
      */
     @Override
     @XmlElement(name = "features")
-    public Set<CharSequence> getFeatures() {
-        return getProperty(FEATURES);
+    public Set<FeatureType> getFeatures() {
+        return getProperty(FeatureType.class, FEATURES);
     }
 
     /**
@@ -363,10 +379,14 @@
      * If and only if the {@code newValue} is non-empty, then this method automatically
      * discards all other properties.
      *
+     * <div class="warning"><b>Upcoming API change:</b>
+     * The type of this property may be changed to {@code Set<CharSequence>} for ISO 19115:2014 conformance.
+     * See <a href="http://jira.codehaus.org/browse/GEO-238">GEO-238</a> for more information.</div>
+     *
      * @param  newValues  the new feature types.
      */
-    public void setFeatures(final Set<? extends CharSequence> newValues) {
-        setProperty(newValues, FEATURES);
+    public void setFeatures(final Set<? extends FeatureType> newValues) {
+        setProperty(newValues, FeatureType.class, FEATURES);
     }
 
     /**
@@ -381,12 +401,16 @@
      * This method returns a modifiable collection only if no other property is set.
      * Otherwise, this method returns an unmodifiable empty collection.
      *
+     * <div class="warning"><b>Upcoming API change:</b>
+     * The type of this property may be changed to {@code Set<CharSequence>} for ISO 19115:2014 conformance.
+     * See <a href="http://jira.codehaus.org/browse/GEO-238">GEO-238</a> for more information.</div>
+     *
      * @return attribute types to which the information applies.
      */
     @Override
     @XmlElement(name = "attributes")
-    public Set<CharSequence> getAttributes() {
-        return getProperty(ATTRIBUTES);
+    public Set<AttributeType> getAttributes() {
+        return getProperty(AttributeType.class, ATTRIBUTES);
     }
 
     /**
@@ -396,10 +420,14 @@
      * If and only if the {@code newValue} is non-empty, then this method automatically
      * discards all other properties.
      *
+     * <div class="warning"><b>Upcoming API change:</b>
+     * The type of this property may be changed to {@code Set<CharSequence>} for ISO 19115:2014 conformance.
+     * See <a href="http://jira.codehaus.org/browse/GEO-238">GEO-238</a> for more information.</div>
+     *
      * @param  newValues  the new attribute types.
      */
-    public void setAttributes(final Set<? extends CharSequence> newValues) {
-        setProperty(newValues, ATTRIBUTES);
+    public void setAttributes(final Set<? extends AttributeType> newValues) {
+        setProperty(newValues, AttributeType.class, ATTRIBUTES);
     }
 
     /**
@@ -414,12 +442,16 @@
      * This method returns a modifiable collection only if no other property is set.
      * Otherwise, this method returns an unmodifiable empty collection.
      *
+     * <div class="warning"><b>Upcoming API change:</b>
+     * The type of this property may be changed to {@code Set<CharSequence>} for ISO 19115:2014 conformance.
+     * See <a href="http://jira.codehaus.org/browse/GEO-238">GEO-238</a> for more information.</div>
+     *
      * @return feature instances to which the information applies.
      */
     @Override
     @XmlElement(name = "featureInstances")
-    public Set<CharSequence> getFeatureInstances() {
-        return getProperty(FEATURE_INSTANCES);
+    public Set<FeatureType> getFeatureInstances() {
+        return getProperty(FeatureType.class, FEATURE_INSTANCES);
     }
 
     /**
@@ -429,10 +461,14 @@
      * If and only if the {@code newValue} is non-empty, then this method automatically
      * discards all other properties.
      *
+     * <div class="warning"><b>Upcoming API change:</b>
+     * The type of this property may be changed to {@code Set<CharSequence>} for ISO 19115:2014 conformance.
+     * See <a href="http://jira.codehaus.org/browse/GEO-238">GEO-238</a> for more information.</div>
+     *
      * @param  newValues  the new feature instances.
      */
-    public void setFeatureInstances(final Set<? extends CharSequence> newValues) {
-        setProperty(newValues, FEATURE_INSTANCES);
+    public void setFeatureInstances(final Set<? extends FeatureType> newValues) {
+        setProperty(newValues, FeatureType.class, FEATURE_INSTANCES);
     }
 
     /**
@@ -447,12 +483,16 @@
      * This method returns a modifiable collection only if no other property is set.
      * Otherwise, this method returns an unmodifiable empty collection.
      *
+     * <div class="warning"><b>Upcoming API change:</b>
+     * The type of this property may be changed to {@code Set<CharSequence>} for ISO 19115:2014 conformance.
+     * See <a href="http://jira.codehaus.org/browse/GEO-238">GEO-238</a> for more information.</div>
+     *
      * @return attribute instances to which the information applies.
      */
     @Override
     @XmlElement(name = "attributeInstances")
-    public Set<CharSequence> getAttributeInstances() {
-        return getProperty(ATTRIBUTE_INSTANCES);
+    public Set<AttributeType> getAttributeInstances() {
+        return getProperty(AttributeType.class, ATTRIBUTE_INSTANCES);
     }
 
     /**
@@ -462,21 +502,29 @@
      * If and only if the {@code newValue} is non-empty, then this method automatically
      * discards all other properties.
      *
+     * <div class="warning"><b>Upcoming API change:</b>
+     * The type of this property may be changed to {@code Set<CharSequence>} for ISO 19115:2014 conformance.
+     * See <a href="http://jira.codehaus.org/browse/GEO-238">GEO-238</a> for more information.</div>
+     *
      * @param  newValues  the new attribute instances.
      */
-    public void setAttributeInstances(final Set<? extends CharSequence> newValues) {
-        setProperty(newValues, ATTRIBUTE_INSTANCES);
+    public void setAttributeInstances(final Set<? extends AttributeType> newValues) {
+        setProperty(newValues, AttributeType.class, ATTRIBUTE_INSTANCES);
     }
 
     /**
      * Returns the class of information that does not fall into the other categories to which the information applies.
      *
+     * <div class="warning"><b>Upcoming API change:</b>
+     * The type of this property may be changed to {@link InternationalString} for ISO 19115:2014 conformance.
+     * See <a href="http://jira.codehaus.org/browse/GEO-221">GEO-221</a> for more information.</div>
+     *
      * @return class of information that does not fall into the other categories, or {@code null}.
      */
     @Override
     @XmlElement(name = "other")
-    public InternationalString getOther() {
-        return (property == OTHER) ? (InternationalString) value : null;
+    public String getOther() {
+        return (property == OTHER) ? (String) value : null;
     }
 
     /**
@@ -487,9 +535,13 @@
      * If and only if the {@code newValue} is non-null, then this method automatically
      * discards all other properties.
      *
+     * <div class="warning"><b>Upcoming API change:</b>
+     * The type of this property may be changed to {@link InternationalString} for ISO 19115:2014 conformance.
+     * See <a href="http://jira.codehaus.org/browse/GEO-221">GEO-221</a> for more information.</div>
+     *
      * @param newValue Other class of information.
      */
-    public void setOther(final InternationalString newValue) {
+    public void setOther(final String newValue) {
         checkWritePermission(value);
         if (newValue != null || property == OTHER) {
             warningOnOverwrite(OTHER);
@@ -523,19 +575,21 @@
             }
             setDataset(description);
         } else if (level == ScopeCode.FEATURE_TYPE) {
-            setFeatures(newValues);
+            setFeatures(LegacyFeatureType.wrapAll(newValues));
         } else if (level == ScopeCode.ATTRIBUTE_TYPE) {
-            setAttributes(newValues);
+            setAttributes(LegacyFeatureType.wrapAll(newValues));
         } else if (level == ScopeCode.FEATURE) {
-            setFeatureInstances(newValues);
+            setFeatureInstances(LegacyFeatureType.wrapAll(newValues));
         } else if (level == ScopeCode.ATTRIBUTE) {
-            setAttributeInstances(newValues);
+            setAttributeInstances(LegacyFeatureType.wrapAll(newValues));
         } else {
-            InternationalString description = null;
+            String description = null;
             if (newValues != null) {
                 for (CharSequence v : newValues) {
-                    description = Types.toInternationalString(v);
-                    if (description != null) break;
+                    if (v != null) {
+                        description = v.toString();
+                        break;
+                    }
                 }
             }
             setOther(description);
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
new file mode 100644
index 0000000..2bf7d32
--- /dev/null
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/maintenance/FeatureTypeAdapter.java
@@ -0,0 +1,49 @@
+/*
+ * 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.iso.maintenance;
+
+import jakarta.xml.bind.annotation.adapters.XmlAdapter;
+import org.opengis.feature.type.FeatureType;
+import org.apache.sis.xml.bind.gco.GO_CharacterString;
+
+
+/**
+ * For (un)marshalling deprecated {@link FeatureType} as a character string,
+ * as expected by ISO 19115-3:2016. This is a temporary bridge to be removed
+ * after the GeoAPI interfaces has been upgraded to ISO 19115-1:2014 model.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ */
+final class FeatureTypeAdapter extends XmlAdapter<GO_CharacterString, FeatureType> {
+    /**
+     * Wrap the given value from {@link DefaultScopeDescription} to the elements
+     * defined by ISO 19115-3:2016 schema.
+     */
+    @Override
+    public FeatureType unmarshal(GO_CharacterString value) {
+        return new LegacyFeatureType(LegacyFeatureType.ADAPTER.unmarshal(value));
+    }
+
+    /**
+     * Unwrap the elements defined by ISO 19115-3:2016 schema to the value used by
+     * {@link DefaultScopeDescription}.
+     */
+    @Override
+    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/maintenance/LegacyFeatureType.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/maintenance/LegacyFeatureType.java
new file mode 100644
index 0000000..1735a1a
--- /dev/null
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/maintenance/LegacyFeatureType.java
@@ -0,0 +1,97 @@
+/*
+ * 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.iso.maintenance;
+
+import java.util.Set;
+import java.util.LinkedHashSet;
+import org.opengis.feature.type.FeatureType;
+import org.opengis.feature.type.AttributeType;
+import org.apache.sis.xml.bind.gco.CharSequenceAdapter;
+import org.apache.sis.util.ArgumentChecks;
+
+
+/**
+ * Bridges between deprecated {@link FeatureType} / {@link AttributeType} and {@link CharSequence}.
+ * {@code FeatureType} and {@code AttributeType} were used in ISO 19115:2003, but have been replaced
+ * by {@link CharSequence} in ISO 19115:2014. The corresponding GeoAPI 3.0 interfaces are empty since
+ * they were placeholder for future work. We use this {@code LegacyFeatureType} as a temporary bridge,
+ * to be removed with GeoAPI 4.0.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.0
+ * @since   1.0
+ *
+ * @deprecated To be removed after migration to GeoAPI 4.0.
+ */
+@Deprecated
+public final class LegacyFeatureType implements FeatureType, AttributeType, CharSequence {
+    /**
+     * The adapter doing most of the actual work of converting {@code FeatureType} or {@code AttributeType}
+     * to {@code <gco:CharacterSequence>} elements.
+     */
+    static final CharSequenceAdapter ADAPTER = new CharSequenceAdapter();
+
+    /**
+     * The value to wrap as a {@code FeatureType} or {@code AttributeType}.
+     */
+    private final CharSequence value;
+
+    /**
+     * Creates a new type for the given value, which must be non-null.
+     *
+     * @param  value  the text to wrap in a legacy feature type.
+     */
+    public LegacyFeatureType(final CharSequence value) {
+        ArgumentChecks.ensureNonNull("value", value);
+        this.value = value;
+    }
+
+    /**
+     * Wraps the given {@code FeatureType} or {@code AttributeType} as a {@code CharSequence}.
+     */
+    static CharSequence wrap(final Object value) {
+        return (value == null || value instanceof CharSequence)
+                ? (CharSequence) value : new LegacyFeatureType(value.toString());
+    }
+
+    /**
+     * Returns a list with all content of the given collection wrapped as {@link LegacyFeatureType}.
+     */
+    static Set<LegacyFeatureType> wrapAll(final Iterable<? extends CharSequence> values) {
+        if (values == null) {
+            return null;
+        }
+        final Set<LegacyFeatureType> wrapped = new LinkedHashSet<>();
+        for (final CharSequence value : values) {
+            wrapped.add((value == null || value instanceof LegacyFeatureType)
+                        ? (LegacyFeatureType) value : new LegacyFeatureType(value));
+        }
+        return wrapped;
+    }
+
+    /**
+     * Delegates to the value given at construction time.
+     */
+    @Override public int          length()                        {return value.length();}
+    @Override public char         charAt(int index)               {return value.charAt(index);}
+    @Override public CharSequence subSequence(int start, int end) {return value.subSequence(start, end);}
+    @Override public String       toString()                      {return value.toString();}
+    @Override public int          hashCode()                      {return value.hashCode() ^ 439703003;}
+    @Override public boolean      equals(final Object obj) {
+        return (obj instanceof LegacyFeatureType) && value.equals(((LegacyFeatureType) obj).value);
+    }
+}
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/maintenance/package-info.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/maintenance/package-info.java
index e74ef43..8a72822 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/maintenance/package-info.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/maintenance/package-info.java
@@ -76,16 +76,18 @@
 @XmlAccessorType(XmlAccessType.NONE)
 @XmlJavaTypeAdapters({
     @XmlJavaTypeAdapter(CI_Date.class),
-    @XmlJavaTypeAdapter(CI_Responsibility.class),
+    @XmlJavaTypeAdapter(CI_ResponsibleParty.class),
     @XmlJavaTypeAdapter(EX_Extent.class),
     @XmlJavaTypeAdapter(GO_DateTime.class),
     @XmlJavaTypeAdapter(MD_MaintenanceFrequencyCode.class),
     @XmlJavaTypeAdapter(MD_Scope.class),
     @XmlJavaTypeAdapter(MD_ScopeCode.class),
     @XmlJavaTypeAdapter(MD_ScopeDescription.class),
-    @XmlJavaTypeAdapter(TM_Duration.class),
+    @XmlJavaTypeAdapter(TM_PeriodDuration.class),
 
     // Java types, primitive types and basic OGC types handling
+    @XmlJavaTypeAdapter(FeatureTypeAdapter.class),
+    @XmlJavaTypeAdapter(AttributeTypeAdapter.class),
     @XmlJavaTypeAdapter(StringAdapter.class),
     @XmlJavaTypeAdapter(CharSequenceAdapter.class),
     @XmlJavaTypeAdapter(InternationalStringAdapter.class)
@@ -105,5 +107,5 @@
 import org.apache.sis.xml.bind.metadata.*;
 import org.apache.sis.xml.bind.metadata.code.*;
 
-// Specific to the geoapi-4.0 branch:
-import org.apache.sis.xml.bind.gts.TM_Duration;
+// Specific to the main and geoapi-3.1 branches:
+import org.apache.sis.xml.bind.gts.TM_PeriodDuration;
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 533359b..e66ec57 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
@@ -102,7 +102,7 @@
     @XmlJavaTypeAdapter(CI_Citation.class),
     @XmlJavaTypeAdapter(CI_Date.class),
     @XmlJavaTypeAdapter(CI_OnlineResource.class),
-    @XmlJavaTypeAdapter(CI_Responsibility.class),
+    @XmlJavaTypeAdapter(CI_ResponsibleParty.class),
     @XmlJavaTypeAdapter(DQ_DataQuality.class),
     @XmlJavaTypeAdapter(GO_DateTime.class),
     @XmlJavaTypeAdapter(GO_Integer.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 544eb2b..9f8f254 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
@@ -27,13 +27,12 @@
 import org.apache.sis.xml.bind.FilterByVersion;
 import org.apache.sis.xml.privy.LegacyNamespaces;
 
+// 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 geoapi-4.0 branch:
-import org.opengis.metadata.maintenance.Scope;
-import org.apache.sis.metadata.iso.maintenance.DefaultScope;
-
 
 /**
  * Quality information for the data specified by a data quality scope.
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 678faae..30d1f40 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
@@ -80,7 +80,7 @@
      *
      * @see #castOrCopy(Scope)
      */
-    public DefaultScope(final Scope object) {
+    public DefaultScope(final org.opengis.metadata.maintenance.Scope object) {
         super(object);
     }
 
@@ -102,7 +102,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 DefaultScope castOrCopy(final Scope object) {
+    public static DefaultScope castOrCopy(final org.opengis.metadata.maintenance.Scope object) {
         if (object == null || object instanceof DefaultScope) {
             return (DefaultScope) object;
         }
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 44b1ef0..682c784 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
@@ -79,7 +79,7 @@
     @XmlJavaTypeAdapter(MD_Format.class),
     @XmlJavaTypeAdapter(MD_Identifier.class),
 //  @XmlJavaTypeAdapter(MD_RangeDimension.class),       // Pending new ISO 19157 revision.
-    @XmlJavaTypeAdapter(MD_Scope.class),
+    @XmlJavaTypeAdapter(MD_Scope.Legacy.class),
     @XmlJavaTypeAdapter(MD_SpatialRepresentation.class),
     @XmlJavaTypeAdapter(MD_SpatialRepresentationTypeCode.class),
     @XmlJavaTypeAdapter(MX_DataFile.class),
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/spatial/DefaultGeorectified.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/spatial/DefaultGeorectified.java
index b5a1180..4dd568e 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/spatial/DefaultGeorectified.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/spatial/DefaultGeorectified.java
@@ -74,7 +74,7 @@
     "checkPointAvailable",
     "checkPointDescription",
     "cornerPoints",
-    "centrePoint",
+    "centerPoint",
     "pointInPixel",
     "transformationDimensionDescription",
     "transformationDimensionMapping",
@@ -86,7 +86,7 @@
     /**
      * Serial number for inter-operability with different versions.
      */
-    private static final long serialVersionUID = 6234748157247202686L;
+    private static final long serialVersionUID = -2924562334097446037L;
 
     /**
      * Mask for the {@code checkPointAvailable} boolean value.
@@ -117,7 +117,7 @@
      * spatial dimensions.
      */
     @SuppressWarnings("serial")
-    private Point centrePoint;
+    private Point centerPoint;
 
     /**
      * Point in a pixel corresponding to the Earth location of the pixel.
@@ -162,7 +162,7 @@
         if (object != null) {
             checkPointDescription              = object.getCheckPointDescription();
             cornerPoints                       = copyList(object.getCornerPoints(), Point.class);
-            centrePoint                        = object.getCentrePoint();
+            centerPoint                        = object.getCenterPoint();
             pointInPixel                       = object.getPointInPixel();
             transformationDimensionDescription = object.getTransformationDimensionDescription();
             transformationDimensionMapping     = copyCollection(object.getTransformationDimensionMapping(), InternationalString.class);
@@ -299,22 +299,30 @@
      * and the grid coordinate of the cell halfway between opposite ends of the grid in the
      * spatial dimensions.
      *
+     * <div class="warning"><b>Upcoming API change</b><br>
+     * This method may be renamed {@code getCentrePoint()} in GeoAPI 4.0
+     * for compliance with ISO 19115:2014.</div>
+     *
      * @return the center point, or {@code null}.
      */
     @Override
     @XmlElement(name = "centrePoint")
-    public Point getCentrePoint() {
-        return centrePoint;
+    public Point getCenterPoint() {
+        return centerPoint;
     }
 
     /**
      * Sets the center point.
      *
+     * <div class="warning"><b>Upcoming API change</b><br>
+     * This method may be renamed {@code setCentrePoint(…)} in GeoAPI 4.0
+     * for compliance with ISO 19115:2014.</div>
+     *
      * @param  newValue  the new center point.
      */
-    public void setCentrePoint(final Point newValue) {
-        checkWritePermission(centrePoint);
-        centrePoint = newValue;
+    public void setCenterPoint(final Point newValue) {
+        checkWritePermission(centerPoint);
+        centerPoint = newValue;
     }
 
     /**
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 d6be1ec..1fe8975 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
@@ -30,6 +30,9 @@
 import org.apache.sis.util.iso.DefaultNameSpace;
 import static org.apache.sis.util.Characters.Filter.LETTERS_AND_DIGITS;
 
+// Specific to the main and geoapi-3.1 branches:
+import org.opengis.referencing.ReferenceIdentifier;
+
 
 /**
  * Does the unobvious mapping between {@link Identifier} properties and {@link GenericName} ones.
@@ -41,7 +44,7 @@
  *
  * @author  Martin Desruisseaux (Geomatys)
  */
-public final class NameToIdentifier implements Identifier {
+public final class NameToIdentifier implements ReferenceIdentifier {
     /**
      * The name from which to infer the identifier attributes.
      */
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 e254073..c1b001c 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
@@ -28,6 +28,10 @@
 import org.apache.sis.util.privy.Constants;
 import static org.apache.sis.util.collection.Containers.isNullOrEmpty;
 
+// Specific to the main and geoapi-3.1 branches:
+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;
 
@@ -50,7 +54,7 @@
      * The primary name by which this object is identified.
      */
     @SuppressWarnings("serial")         // Most SIS implementations are serializable.
-    protected Identifier name;
+    protected ReferenceIdentifier name;
 
     /**
      * Creates an identified object without identifier.
@@ -73,7 +77,7 @@
      *
      * @param  name  the primary name by which this object is identified.
      */
-    public SimpleIdentifiedObject(final Identifier name) {
+    public SimpleIdentifiedObject(final ReferenceIdentifier name) {
         this.name = name;
     }
 
@@ -83,11 +87,43 @@
      * @return the identifier given at construction time.
      */
     @Override
-    public Identifier getName() {
+    public ReferenceIdentifier getName() {
         return name;
     }
 
     /**
+     * Method required by most {@link IdentifiedObject} sub-interfaces.
+     * Current implementation returns {@code null}.
+     *
+     * <p>If a future version allows this method to return a non-null value,
+     * revisit {@link #equals(Object, ComparisonMode)} in subclasses.</p>
+     *
+     * @return the domain of validity, or {@code null} if none.
+     *
+     * @deprecated Removed from ISO 19111:2019 (moved to {@code ObjectDomain}).
+     */
+    @Deprecated
+    public final Extent getDomainOfValidity() {
+        return null;
+    }
+
+    /**
+     * Method required by most {@link IdentifiedObject} sub-interfaces.
+     * Current implementation returns {@code null}.
+     *
+     * <p>If a future version allows this method to return a non-null value,
+     * revisit {@link #equals(Object, ComparisonMode)} in subclasses.</p>
+     *
+     * @return the scope, or {@code null} if none.
+     *
+     * @deprecated Removed from ISO 19111:2019 (moved to {@code ObjectDomain}).
+     */
+    @Deprecated
+    public final InternationalString getScope() {
+        return null;
+    }
+
+    /**
      * Returns a narrative explanation of the role of this object.
      * The default implementation returns {@link Identifier#getDescription()}.
      *
@@ -148,7 +184,7 @@
                 return Objects.equals(getName(), that.getName()) &&
                         isNullOrEmpty(that.getIdentifiers()) &&
                         isNullOrEmpty(that.getAlias()) &&
-                        that.getRemarks().isEmpty();
+                        that.getRemarks() == null;
             }
         }
         return false;
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 8335b1c..c2b49c3 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
@@ -25,17 +25,16 @@
 import org.apache.sis.util.Deprecable;
 import org.apache.sis.util.privy.Constants;
 
-// Specific to the geoapi-4.0 branch:
-import java.util.Optional;
-import org.opengis.metadata.Identifier;
+// Specific to the main and geoapi-3.1 branches:
+import org.opengis.referencing.ReferenceIdentifier;
 
 
 /**
- * An implementation of {@link Identifier} as a wrapper around a {@link Citation}.
+ * An implementation of {@link ReferenceIdentifier} as a wrapper around a {@link Citation}.
  *
  * @author  Martin Desruisseaux (Geomatys)
  */
-public class SimpleIdentifier implements Identifier, Deprecable, Serializable {
+public class SimpleIdentifier implements ReferenceIdentifier, Deprecable, Serializable {
     /**
      * For cross-version compatibility.
      */
@@ -142,8 +141,8 @@
      * An optional free text.
      */
     @Override
-    public Optional<InternationalString> getRemarks() {
-        return Optional.empty();
+    public InternationalString getRemarks() {
+        return null;
     }
 
     /**
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 d33e5e4..2a89923 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
@@ -29,14 +29,14 @@
 import org.opengis.metadata.spatial.SpatialRepresentationType;
 import org.opengis.util.InternationalString;
 
+// 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 geoapi-4.0 branch:
-import org.opengis.metadata.citation.Responsibility;
-
 
 /**
  * An empty implementation of ISO 19115 metadata for dataset (not for services).
@@ -109,7 +109,7 @@
      * Parties responsible for the metadata information.
      */
     @Override
-    public Collection<Responsibility> getContacts() {
+    public Collection<ResponsibleParty> getContacts() {
         return Collections.emptyList();
     }
 
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 d329d38..b6d43c1 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
@@ -33,6 +33,12 @@
 import org.apache.sis.system.Semaphores;
 import org.apache.sis.metadata.internal.Dependencies;
 
+// 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;
+
 
 /**
  * The handler for metadata proxy that implement (indirectly) metadata interfaces like
@@ -125,7 +131,7 @@
      * @return the value to be returned from the public method invoked by the method.
      */
     @Override
-    public Object invoke(final Object proxy, final Method method, final Object[] args) {
+    public Object invoke(final Object proxy, Method method, final Object[] args) {
         final int n = (args != null) ? args.length : 0;
         switch (method.getName()) {
             case "toString": {
@@ -154,6 +160,8 @@
                  */
                 Object value;
                 try {
+                    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);
@@ -313,4 +321,25 @@
     public String toString() {
         return toString(getClass());
     }
+
+    /**
+     * If the given method is superceded by a new method, the new method.
+     * This is a hack for transition from legacy ISO type to newer type:
+     * {@code ResponsibleParty.getRole()} overriding {@code Responsibility.getRole()}
+     * confuses this {@code Dispatcher} class. We need the method in the base interface.
+     */
+    private static Method supercede(Method method) throws NoSuchMethodException {
+        if (method.getDeclaringClass() == ResponsibleParty.class) {
+            if ("getRole".equals(method.getName())) {
+                method = Responsibility.class.getMethod("getRole");
+            } else {
+                /*
+                 * `getIndividualName()`, `getOrganisationName()`, `getPositionName()` and
+                 * `getContactInfo()` have no direct equivalence in `Responsibility` class.
+                 */
+                return null;
+            }
+        }
+        return method;
+    }
 }
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 5ce03ca..f2e9856 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
@@ -29,12 +29,12 @@
 import org.apache.sis.util.iso.Types;
 import org.apache.sis.xml.NilReason;
 
+// 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 geoapi-4.0 branch:
-import org.apache.sis.metadata.iso.citation.DefaultResponsibility;
-
 
 /**
  * A fallback providing hard-coded values of metadata entities.
@@ -219,8 +219,9 @@
         if (code                  != null) c.setIdentifiers(singleton(new DefaultIdentifier(codeSpace, code, version)));
         if (presentationForm      != null) c.setPresentationForms(singleton(presentationForm));
         if (citedResponsibleParty != null) {
-            c.setCitedResponsibleParties(singleton(new DefaultResponsibility(Role.PRINCIPAL_INVESTIGATOR, null,
-                    new DefaultOrganisation(citedResponsibleParty, null, null, null))));
+            final DefaultResponsibleParty r = new DefaultResponsibleParty(Role.PRINCIPAL_INVESTIGATOR);
+            r.setParties(singleton(new DefaultOrganisation(citedResponsibleParty, null, null, null)));
+            c.setCitedResponsibleParties(singleton(r));
         }
         if (copyFrom != null) {
             c.setCitedResponsibleParties(createCitation(copyFrom).getCitedResponsibleParties());
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
new file mode 100644
index 0000000..0687530
--- /dev/null
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/temporal/DefaultPeriodDuration.java
@@ -0,0 +1,71 @@
+/*
+ * 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.io.Serializable;
+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;
+ * GeoAPI temporal interfaces are expected to change a lot in a future revision.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ */
+@SuppressWarnings("serial")
+public final class DefaultPeriodDuration implements PeriodDuration, Serializable {
+    /**
+     * The temporal object providing the duration value.
+     */
+    public final TemporalAmount duration;
+
+    /**
+     * Creates a new duration.
+     */
+    public DefaultPeriodDuration(final TemporalAmount duration) {
+        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();
+    }
+
+    /** Hash code value of the time position. */
+    @Override public int hashCode() {
+        return duration.hashCode() ^ 879337943;
+    }
+
+    /** Compares with given object for equality. */
+    @Override public boolean equals(final Object obj) {
+        if (obj instanceof DefaultPeriodDuration) {
+            duration.equals(((DefaultPeriodDuration) obj).duration);
+        }
+        return false;
+    }
+}
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
index 0a8220d..f85bd9f 100644
--- 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
@@ -21,8 +21,8 @@
 import org.opengis.temporal.TemporalGeometricPrimitive;
 import org.opengis.temporal.TemporalPrimitive;
 
-// Specific to the geoapi-4.0 branch:
-import org.opengis.metadata.Identifier;
+// Specific to the geoapi-3.1 branch:
+import org.opengis.referencing.ReferenceIdentifier;
 
 
 /**
@@ -31,7 +31,7 @@
  *
  * @author  Martin Desruisseaux (Geomatys)
  */
-abstract class Primitive implements TemporalGeometricPrimitive, Identifier {
+abstract class Primitive implements TemporalGeometricPrimitive, ReferenceIdentifier {
     /**
      * For sub-class constructors.
      */
@@ -43,7 +43,7 @@
      * This field is inherited from ISO 19111 {@code IdentifiedObject} and is in principle mandatory.
      */
     @Override
-    public final Identifier getName() {
+    public final ReferenceIdentifier getName() {
         return this;
     }
 
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 baedbaa..591aee1 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
@@ -41,6 +41,16 @@
     }
 
     /**
+     * Creates an instant for the given date.
+     *
+     * @param  time  the date for which to create instant, or {@code null}.
+     * @return the instant, or {@code null} if the given time was null.
+     */
+    public static TemporalPrimitive createInstant(final Date time) {
+        return (time == null) ? null : createInstant(time.toInstant());
+    }
+
+    /**
      * Creates an instant for the given Java temporal instant.
      *
      * @param  time  the date for which to create instant, or {@code null}.
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/cat/CodeListAdapter.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/cat/CodeListAdapter.java
index 9adc3ef..ffd8e31 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/cat/CodeListAdapter.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/cat/CodeListAdapter.java
@@ -22,6 +22,9 @@
 import org.apache.sis.xml.bind.Context;
 import org.apache.sis.xml.bind.FilterByVersion;
 
+// Specific to the main and geoapi-3.1 branches:
+import org.apache.sis.util.iso.Types;
+
 
 /**
  * An adapter for {@link CodeList}, in order to implement the ISO 19115-3 standard.
@@ -128,7 +131,33 @@
      */
     @Override
     public final ValueType marshal(final BoundType code) {
-        return (code != null) ? wrap(new CodeListUID(Context.current(), code)) : null;
+        if (code == null) {
+            return null;
+        }
+        final CodeListUID p;
+        if (isEnum()) {
+            // To be removed after GEO-199 resolution.
+            p = new CodeListUID();
+            p.value = Types.getCodeName(code);
+        } else {
+            p = new CodeListUID(Context.current(), code);
+        }
+        return wrap(p);
+    }
+
+    /**
+     * Returns {@code true} if this code list is actually an enum. The default implementation
+     * returns {@code false} in every cases, since there is very few enums in ISO 19115.
+     *
+     * @return {@code true} if this code list is actually an enum.
+     *
+     * @todo Remove this method after we refactored enum wrappers as {@code EnumAdapter} subclasses
+     *       instead of {@code CodeListAdapter}. This requires the resolution of GEO-199 first.
+     *
+     * @see <a href="http://jira.codehaus.org/browse/GEO-199">GEO-199</a>
+     */
+    protected boolean isEnum() {
+        return false;
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/gco/PropertyType.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/gco/PropertyType.java
index 403f229..e208a99 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/gco/PropertyType.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/gco/PropertyType.java
@@ -625,7 +625,8 @@
      * @throws URISyntaxException if a URI cannot be parsed.
      */
     @Override
-    public final BoundType unmarshal(final ValueType value) throws URISyntaxException {
+    // Overridden by `MD_Scope` on the geoapi-3.x branches only.
+    public BoundType unmarshal(final ValueType value) throws URISyntaxException {
         return (value != null) ? value.resolve(Context.current()) : null;
     }
 
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/gmi/MI_Band.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/gmi/MI_Band.java
index 05a28c0..4b1265a 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/gmi/MI_Band.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/gmi/MI_Band.java
@@ -66,8 +66,8 @@
             if (original.getBandBoundaryDefinition()   != null ||
                 original.getNominalSpatialResolution() != null ||
                 original.getTransferFunctionType()     != null ||
-                original.getTransmittedPolarisation()  != null ||
-                original.getDetectedPolarisation()     != null)
+                original.getTransmittedPolarization()  != null ||
+                original.getDetectedPolarization()     != null)
             {
                 return new MI_Band(original);
             }
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/gts/TM_Duration.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/gts/TM_Duration.java
index 6f94b03..1673c6b 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/gts/TM_Duration.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/gts/TM_Duration.java
@@ -50,7 +50,7 @@
      *
      * @param  metadata  the metadata value to marshal.
      */
-    private TM_Duration(final TemporalAmount metadata) {
+    TM_Duration(final TemporalAmount metadata) {
         super(metadata);
     }
 
@@ -76,6 +76,13 @@
     }
 
     /**
+     * Returns the wrapped metadata value.
+     */
+    final TemporalAmount get() {
+        return metadata;
+    }
+
+    /**
      * Returns the {@code Duration} generated from the metadata value.
      * This method is systematically called at marshalling time by JAXB.
      *
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/gts/TM_PeriodDuration.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/gts/TM_PeriodDuration.java
new file mode 100644
index 0000000..472c346
--- /dev/null
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/gts/TM_PeriodDuration.java
@@ -0,0 +1,100 @@
+/*
+ * 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.gts;
+
+import java.time.temporal.TemporalAmount;
+import javax.xml.datatype.Duration;
+import jakarta.xml.bind.annotation.XmlElement;
+import org.opengis.temporal.PeriodDuration;
+import org.apache.sis.pending.temporal.DefaultPeriodDuration;
+import org.apache.sis.xml.bind.gco.PropertyType;
+
+
+/**
+ * Wraps a {@code gts:TM_PeriodDuration} element.
+ *
+ * @author  Guilhem Legal (Geomatys)
+ * @author  Martin Desruisseaux (Geomatys)
+ */
+public final class TM_PeriodDuration extends PropertyType<TM_PeriodDuration, PeriodDuration> {
+    /**
+     * Empty constructor for JAXB.
+     */
+    public TM_PeriodDuration() {
+    }
+
+    /**
+     * Wraps a Temporal Period Duration value at marshalling-time.
+     *
+     * @param  metadata  the metadata value to marshal.
+     */
+    private TM_PeriodDuration(final PeriodDuration metadata) {
+        super(metadata);
+    }
+
+    /**
+     * Returns the Period Duration value wrapped by a {@code gts:TM_PeriodDuration} element.
+     *
+     * @param  value  the value to marshal.
+     * @return the adapter which wraps the metadata value.
+     */
+    @Override
+    protected TM_PeriodDuration wrap(final PeriodDuration value) {
+        return new TM_PeriodDuration(value);
+    }
+
+    /**
+     * Returns the GeoAPI interface which is bound by this adapter.
+     *
+     * @return {@code PeriodDuration.class}
+     */
+    @Override
+    protected final Class<PeriodDuration> getBoundType() {
+        return PeriodDuration.class;
+    }
+
+    /**
+     * Returns the {@link Duration} generated from the metadata value.
+     * This method is systematically called at marshalling time by JAXB.
+     *
+     * @return the time period, or {@code null}.
+     */
+    @XmlElement(name = "TM_PeriodDuration")
+    public final Duration getElement() {
+        if (metadata instanceof TemporalAmount) {
+            return new TM_Duration((TemporalAmount) metadata).getElement();
+        }
+        return null;
+    }
+
+    /**
+     * Sets the value from the {@link Duration}.
+     * This method is called at unmarshalling time by JAXB.
+     *
+     * @param  duration  the adapter to set.
+     */
+    public final void setElement(final Duration duration) {
+        var p = new TM_Duration();
+        p.setElement(duration);
+        TemporalAmount t = p.get();
+        if (t == null || t instanceof PeriodDuration) {
+            metadata = (PeriodDuration) t;
+        } else {
+            metadata = new DefaultPeriodDuration(t);
+        }
+    }
+}
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/CI_ResponsibleParty.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/CI_ResponsibleParty.java
index fc65807..6e349ce 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/CI_ResponsibleParty.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/CI_ResponsibleParty.java
@@ -33,14 +33,10 @@
  * JAXB adapter mapping implementing class to a legacy GeoAPI interface.
  * See package documentation for more information about JAXB and interface.
  *
- * @deprecated This adapter is not used anymore for ISO 19115-3:2014 metadata.
- * However, it is needed for branches that depend on GeoAPI 3.x, and is also needed
- * for implementing web services that have not yet been upgraded to latest ISO standard.
- *
  * @author  Cédric Briançon (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  */
-@Deprecated(since="1.0")
+@SuppressWarnings("deprecation")
 public final class CI_ResponsibleParty extends PropertyType<CI_ResponsibleParty, ResponsibleParty> {
     /**
      * Empty constructor for JAXB only.
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 1276823..de496bb 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
@@ -20,6 +20,9 @@
 import org.apache.sis.metadata.iso.maintenance.DefaultScope;
 import org.apache.sis.xml.bind.gco.PropertyType;
 
+// 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;
 
@@ -91,6 +94,21 @@
     }
 
     /**
+     * On unmarshalling, creates an instance of the deprecated
+     * {@link org.apache.sis.metadata.iso.quality.DefaultScope} subclass.
+     */
+    public static final class Legacy extends MD_Scope {
+        /** Empty constructor used only by JAXB. */
+        public Legacy() {
+        }
+
+        /** 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));
+        }
+    }
+
+    /**
      * Wraps the value only if marshalling an element from the ISO 19115:2014 metadata model.
      * Otherwise (i.e. if marshalling according legacy ISO 19115:2003 model), omits the element.
      */
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/code/MD_MediumNameCode.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/code/MD_MediumNameCode.java
index 71a73d4..4c839f2 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/code/MD_MediumNameCode.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/code/MD_MediumNameCode.java
@@ -21,8 +21,8 @@
 import org.apache.sis.xml.bind.cat.CodeListUID;
 import org.apache.sis.xml.privy.LegacyNamespaces;
 
-// Specific to the geoapi-4.0 branch:
-import org.apache.sis.metadata.iso.legacy.MediumName;
+// Specific to the main and geoapi-3.1 branches:
+import org.opengis.metadata.distribution.MediumName;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/code/MD_PixelOrientationCode.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/code/MD_PixelOrientationCode.java
index d2c95ab..4697d72 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/code/MD_PixelOrientationCode.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/code/MD_PixelOrientationCode.java
@@ -20,8 +20,9 @@
 import org.opengis.metadata.spatial.PixelOrientation;
 import org.apache.sis.xml.Namespaces;
 
-// Specific to the geoapi-4.0 branch:
-import org.apache.sis.xml.bind.cat.EnumAdapter;
+// Specific to the main and geoapi-3.1 branches:
+import org.apache.sis.xml.bind.cat.CodeListAdapter;
+import org.apache.sis.xml.bind.cat.CodeListUID;
 
 
 /**
@@ -32,13 +33,9 @@
  * @author  Cédric Briançon (Geomatys)
  * @author  Cullen Rombach (Image Matters)
  */
-public final class MD_PixelOrientationCode extends EnumAdapter<MD_PixelOrientationCode, PixelOrientation> {
-    /**
-     * The enumeration value.
-     */
-    @XmlElement(name = "MD_PixelOrientationCode", namespace = Namespaces.MSR)
-    public String value;
-
+public final class MD_PixelOrientationCode
+        extends CodeListAdapter<MD_PixelOrientationCode, PixelOrientation>
+{
     /**
      * Empty constructor for JAXB only.
      */
@@ -46,29 +43,57 @@
     }
 
     /**
-     * Returns the wrapped value.
-     *
-     * @param  wrapper  the wrapper.
-     * @return the wrapped value.
+     * Creates a new adapter for the given proxy.
      */
-    @Override
-    public final PixelOrientation unmarshal(final MD_PixelOrientationCode wrapper) {
-        return PixelOrientation.valueOf(name(wrapper.value));
+    private MD_PixelOrientationCode(final CodeListUID value) {
+        super(value);
     }
 
     /**
-     * Wraps the given value.
+     * {@inheritDoc}
      *
-     * @param  e  the value to wrap.
-     * @return the wrapped value.
+     * @return the wrapper for the code list value.
      */
     @Override
-    public final MD_PixelOrientationCode marshal(final PixelOrientation e) {
-        if (e == null) {
-            return null;
-        }
-        final MD_PixelOrientationCode wrapper = new MD_PixelOrientationCode();
-        wrapper.value = value(e);
-        return wrapper;
+    protected MD_PixelOrientationCode wrap(final CodeListUID value) {
+        return new MD_PixelOrientationCode(value);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @return the code list class.
+     */
+    @Override
+    protected Class<PixelOrientation> getCodeListClass() {
+        return PixelOrientation.class;
+    }
+
+    /**
+     * Returns {@code true} since this code list is actually an enum.
+     */
+    @Override
+    protected boolean isEnum() {
+        return true;
+    }
+
+    /**
+     * Invoked by JAXB on marshalling.
+     *
+     * @return The value to be marshalled.
+     */
+    @Override
+    @XmlElement(name = "MD_PixelOrientationCode", namespace = Namespaces.MSR)
+    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_TopicCategoryCode.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/code/MD_TopicCategoryCode.java
index b1d51b0..1226d29 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/code/MD_TopicCategoryCode.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/code/MD_TopicCategoryCode.java
@@ -20,8 +20,9 @@
 import org.opengis.metadata.identification.TopicCategory;
 import org.apache.sis.xml.Namespaces;
 
-// Specific to the geoapi-4.0 branch:
-import org.apache.sis.xml.bind.cat.EnumAdapter;
+// Specific to the main and geoapi-3.1 branches:
+import org.apache.sis.xml.bind.cat.CodeListAdapter;
+import org.apache.sis.xml.bind.cat.CodeListUID;
 
 
 /**
@@ -33,13 +34,7 @@
  * @author  Guihem Legal (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  */
-public final class MD_TopicCategoryCode extends EnumAdapter<MD_TopicCategoryCode, TopicCategory> {
-    /**
-     * The enumeration value.
-     */
-    @XmlElement(name = "MD_TopicCategoryCode", namespace = Namespaces.MRI)
-    public String value;
-
+public final class MD_TopicCategoryCode extends CodeListAdapter<MD_TopicCategoryCode, TopicCategory> {
     /**
      * Empty constructor for JAXB only.
      */
@@ -47,29 +42,57 @@
     }
 
     /**
-     * Returns the wrapped value.
-     *
-     * @param  wrapper  the wrapper.
-     * @return the wrapped value.
+     * Creates a new adapter for the given proxy.
      */
-    @Override
-    public final TopicCategory unmarshal(final MD_TopicCategoryCode wrapper) {
-        return TopicCategory.valueOf(name(wrapper.value));
+    private MD_TopicCategoryCode(final CodeListUID value) {
+        super(value);
     }
 
     /**
-     * Wraps the given value.
+     * {@inheritDoc}
      *
-     * @param  e  the value to wrap.
-     * @return the wrapped value.
+     * @return the wrapper for the code list value.
      */
     @Override
-    public final MD_TopicCategoryCode marshal(final TopicCategory e) {
-        if (e == null) {
-            return null;
-        }
-        final MD_TopicCategoryCode wrapper = new MD_TopicCategoryCode();
-        wrapper.value = value(e);
-        return wrapper;
+    protected MD_TopicCategoryCode wrap(final CodeListUID value) {
+        return new MD_TopicCategoryCode(value);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @return the code list class.
+     */
+    @Override
+    protected Class<TopicCategory> getCodeListClass() {
+        return TopicCategory.class;
+    }
+
+    /**
+     * Returns {@code true} since this code list is actually an enum.
+     */
+    @Override
+    protected boolean isEnum() {
+        return true;
+    }
+
+    /**
+     * Invoked by JAXB on marshalling.
+     *
+     * @return The value to be marshalled.
+     */
+    @Override
+    @XmlElement(name = "MD_TopicCategoryCode", namespace = Namespaces.MRI)
+    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/MI_PolarisationOrientationCode.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/code/MI_PolarisationOrientationCode.java
index 7bc30a4..160e7dd 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/code/MI_PolarisationOrientationCode.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/code/MI_PolarisationOrientationCode.java
@@ -21,12 +21,12 @@
 import org.apache.sis.xml.bind.cat.CodeListAdapter;
 import org.apache.sis.xml.bind.cat.CodeListUID;
 
-// Specific to the geoapi-4.0 branch:
-import org.opengis.metadata.content.PolarisationOrientation;
+// Specific to the main and geoapi-3.1 branches:
+import org.opengis.metadata.content.PolarizationOrientation;
 
 
 /**
- * JAXB adapter for {@link PolarisationOrientation}
+ * JAXB adapter for {@link PolarizationOrientation}
  * 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.
  *
@@ -36,7 +36,7 @@
  * @see <a href="https://issues.apache.org/jira/browse/SIS-398">SIS-398</a>
  */
 public final class MI_PolarisationOrientationCode
-        extends CodeListAdapter<MI_PolarisationOrientationCode, PolarisationOrientation>
+        extends CodeListAdapter<MI_PolarisationOrientationCode, PolarizationOrientation>
 {
     /**
      * Empty constructor for JAXB only.
@@ -67,8 +67,8 @@
      * @return the code list class.
      */
     @Override
-    protected Class<PolarisationOrientation> getCodeListClass() {
-        return PolarisationOrientation.class;
+    protected Class<PolarizationOrientation> getCodeListClass() {
+        return PolarizationOrientation.class;
     }
 
     /**
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 498d50c..9f92939 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
@@ -33,6 +33,9 @@
 import org.apache.sis.util.Classes;
 import org.apache.sis.util.iso.Names;
 
+// 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;
@@ -165,9 +168,9 @@
      * @return the parameter name as an identifier (the type specified by ISO 19111).
      */
     @Override
-    public synchronized Identifier getName() {
+    public synchronized ReferenceIdentifier getName() {
         if (name == null && code != null) {
-            var id = new DefaultIdentifier(code);
+            final var id = new RS_Identifier(null, code, null);
             id.setDescription(definition);
             id.transitionTo(DefaultIdentifier.State.FINAL);
             name = id;
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/replace/RS_Identifier.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/replace/RS_Identifier.java
index 25904c5..b3f9f0c 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/replace/RS_Identifier.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/replace/RS_Identifier.java
@@ -23,6 +23,10 @@
 import org.apache.sis.metadata.iso.*;
 import org.apache.sis.xml.privy.LegacyNamespaces;
 
+// Specific to the main and geoapi-3.1 branches:
+import org.opengis.metadata.citation.Citation;
+import org.opengis.referencing.ReferenceIdentifier;
+
 
 /**
  * Identifier using {@code <gmd:RS_Identifier>} XML element name.
@@ -48,7 +52,7 @@
 @TitleProperty(name = "code")
 @XmlType(name = "RS_Identifier_Type", namespace = LegacyNamespaces.GMD)
 @XmlRootElement(name = "RS_Identifier", namespace = LegacyNamespaces.GMD)
-public final class RS_Identifier extends DefaultIdentifier {
+public final class RS_Identifier extends DefaultIdentifier implements ReferenceIdentifier {
     /**
      * For cross-version compatibility.
      */
@@ -61,6 +65,27 @@
     }
 
     /**
+     * Creates a new identifier initialized to the given code, code space and version number.
+     *
+     * @param codeSpace  identifier or namespace in which the code is valid, or {@code null} if not available.
+     * @param code       alphanumeric value identifying an instance in the namespace, or {@code null} if none.
+     * @param version    the version identifier for the namespace as specified by the code authority, or {@code null} if none.
+     */
+    public RS_Identifier(final String codeSpace, final String code, final String version) {
+        super(codeSpace, code, version);
+    }
+
+    /**
+     * Creates an identifier initialized to the given authority and code.
+     *
+     * @param authority  the the person or party responsible for maintenance of the namespace, or {@code null} if none.
+     * @param code       the alphanumeric value identifying an instance in the namespace, or {@code null} if none.
+     */
+    public RS_Identifier(final Citation authority, final String code) {
+        super(authority, code);
+    }
+
+    /**
      * Creates a new identifier from the specified one.
      *
      * @see #wrap(Identifier)
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 ae167e3..47c6061 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
@@ -27,12 +27,12 @@
 import org.apache.sis.util.ComparisonMode;
 import org.apache.sis.util.Utilities;
 
+// 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;
 
-// Specific to the geoapi-4.0 branch:
-import org.opengis.metadata.Identifier;
-
 
 /**
  * An implementation of {@link ReferenceSystem} marshalled as specified in ISO 19115.
@@ -93,7 +93,7 @@
      *
      * @param  name  the primary name by which this object is identified.
      */
-    public ReferenceSystemMetadata(final Identifier name) {
+    public ReferenceSystemMetadata(final ReferenceIdentifier name) {
         super(name);
     }
 
@@ -114,8 +114,8 @@
      */
     @Override
     @XmlElement(name = "referenceSystemIdentifier")
-    public final Identifier getName() {
-        Identifier name = super.getName();
+    public final ReferenceIdentifier getName() {
+        ReferenceIdentifier name = super.getName();
         if (isLegacyMetadata) {
             name = RS_Identifier.wrap(name);
         }
@@ -127,7 +127,7 @@
      *
      * @param  name  the new primary name.
      */
-    public final void setName(final Identifier name) {
+    public final void setName(final ReferenceIdentifier name) {
         this.name = name;
     }
 
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 b84bcb2..8621ef0 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
@@ -35,6 +35,9 @@
 import org.apache.sis.util.iso.Names;
 import static org.apache.sis.util.privy.CollectionsExt.nonNull;
 
+// 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;
@@ -239,10 +242,10 @@
      * @return the parameter name as an identifier (the type specified by ISO 19111).
      */
     @Override
-    public synchronized Identifier getName() {
+    public synchronized ReferenceIdentifier getName() {
         if (name == null && memberName != null) {
-            if (memberName instanceof Identifier) {
-                name = (Identifier) memberName;
+            if (memberName instanceof ReferenceIdentifier) {
+                name = (ReferenceIdentifier) memberName;
             } else {
                 name = new NameToIdentifier(memberName);
             }
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 08732eb..36e066e 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
@@ -35,13 +35,13 @@
 import static org.junit.jupiter.api.Assertions.*;
 import org.apache.sis.test.TestCase;
 
+// Specific to the main and geoapi-3.1 branches:
+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;
 
-// Specific to the geoapi-4.0 branch:
-import org.opengis.metadata.citation.Responsibility;
-import org.apache.sis.metadata.iso.citation.DefaultResponsibility;
-
 
 /**
  * Tests the {@link HashCode} class. This is also used as a relatively simple {@link MetadataVisitor} test.
@@ -90,8 +90,9 @@
         final var title    = new SimpleInternationalString("Some title");
         final var person   = new SimpleInternationalString("Illustre inconnu");
         final var party    = new DefaultIndividual(person, null, null);
-        final var resp     = new DefaultResponsibility(Role.AUTHOR, null, party);
+        final var resp     = new DefaultResponsibleParty(Role.AUTHOR);
         final var instance = new DefaultCitation(title);
+        resp.getParties().add(party);
         instance.getCitedResponsibleParties().add(resp);
         /*
          * Individual hash code is the sum of all its properties, none of them being a collection.
@@ -101,7 +102,7 @@
         /*
          * The +31 below come from java.util.List contract, since above Individual is a list member.
          */
-        expected += Responsibility.class.hashCode() + Role.AUTHOR.hashCode() + 31;
+        expected += ResponsibleParty.class.hashCode() + Role.AUTHOR.hashCode() + 31;
         assertEquals(Integer.valueOf(expected), hash(resp));
         /*
          * The +31 below come from java.util.List contract, since above Responsibility is a list member.
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 1d0cf3a..229c4a6 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
@@ -63,14 +63,15 @@
 import static org.apache.sis.test.TestUtilities.getSingleton;
 import static org.apache.sis.metadata.Assertions.assertTitleEquals;
 
+// Specific to the main and geoapi-3.1 branches:
+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 geoapi-4.0 branch:
-import org.opengis.metadata.citation.Responsibility;
-
 
 /**
  * Tests the {@link PropertyAccessor} class. Every tests in this class instantiates directly a
@@ -183,10 +184,10 @@
             Citation.class, "getEdition",                 "edition",                 "edition",               "Edition",                    InternationalString.class,
             Citation.class, "getEditionDate",             "editionDate",             "editionDate",           "Edition date",               Date.class,
             Citation.class, "getIdentifiers",             "identifiers",             "identifier",            "Identifiers",                Identifier[].class,
-            Citation.class, "getCitedResponsibleParties", "citedResponsibleParties", "citedResponsibleParty", "Cited responsible parties",  Responsibility[].class,
+            Citation.class, "getCitedResponsibleParties", "citedResponsibleParties", "citedResponsibleParty", "Cited responsible parties",  ResponsibleParty[].class,
             Citation.class, "getPresentationForms",       "presentationForms",       "presentationForm",      "Presentation forms",         PresentationForm[].class,
             Citation.class, "getSeries",                  "series",                  "series",                "Series",                     Series.class,
-            Citation.class, "getOtherCitationDetails",    "otherCitationDetails",    "otherCitationDetails",  "Other citation details",     InternationalString[].class,
+            Citation.class, "getOtherCitationDetails",    "otherCitationDetails",    "otherCitationDetails",  "Other citation details",     InternationalString.class,
 //          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,
@@ -210,9 +211,9 @@
             Identification.class, "getCitation",                   "citation",                   "citation",                  "Citation",                     Citation.class,
             Identification.class, "getAbstract",                   "abstract",                   "abstract",                  "Abstract",                     InternationalString.class,
             Identification.class, "getPurpose",                    "purpose",                    "purpose",                   "Purpose",                      InternationalString.class,
-            Identification.class, "getCredits",                    "credits",                    "credit",                    "Credits",                      InternationalString[].class,
+            Identification.class, "getCredits",                    "credits",                    "credit",                    "Credits",                      String[].class,
             Identification.class, "getStatus",                     "status",                     "status",                    "Status",                       Progress[].class,
-            Identification.class, "getPointOfContacts",            "pointOfContacts",            "pointOfContact",            "Point of contacts",            Responsibility[].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,
@@ -244,9 +245,9 @@
             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",              Identifier.class,
+            IdentifiedObject.class, "getName",             "name",             "name",               "Name",              ReferenceIdentifier.class,
             IdentifiedObject.class, "getAlias",            "alias",            "alias",              "Alias",             GenericName[].class,
-            IdentifiedObject.class, "getIdentifiers",      "identifiers",      "identifier",         "Identifiers",       Identifier[].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);
     }
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/PropertyInformationTest.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/PropertyInformationTest.java
index 25bb3b2..0c2e724 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/PropertyInformationTest.java
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/PropertyInformationTest.java
@@ -38,8 +38,8 @@
 import static org.apache.sis.metadata.Assertions.assertTitleEquals;
 import static org.apache.sis.test.TestUtilities.getSingleton;
 
-// Specific to the geoapi-4.0 branch:
-import org.opengis.annotation.Obligation;
+// Specific to the main and geoapi-3.1 branches:
+import org.opengis.metadata.Obligation;
 
 
 /**
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 c776b19..34b2cad 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
@@ -37,9 +37,6 @@
 import org.apache.sis.test.TestUtilities;
 import org.apache.sis.test.TestCase;
 
-// Specific to the geoapi-4.0 branch:
-import java.util.Set;
-
 
 /**
  * Tests the {@link TreeNodeChildren} class.
@@ -72,7 +69,7 @@
     static DefaultCitation metadataWithoutCollections() {
         final DefaultCitation citation = new DefaultCitation("Some title");
         citation.setEdition(new SimpleInternationalString("Some edition"));
-        citation.setOtherCitationDetails(Set.of(new SimpleInternationalString("Some other details")));
+        citation.setOtherCitationDetails(new SimpleInternationalString("Some other details"));
         return citation;
     }
 
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 c4e21e2..5e97e57 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
@@ -41,13 +41,13 @@
 import static org.apache.sis.test.Assertions.assertMessageContains;
 import static org.apache.sis.metadata.Assertions.assertTitleEquals;
 
+// Specific to the main and geoapi-3.1 branches:
+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;
 
-// Specific to the geoapi-4.0 branch:
-import org.opengis.metadata.citation.Responsibility;
-import org.apache.sis.metadata.iso.citation.DefaultResponsibility;
-
 
 /**
  * Tests the {@link TreeNode} class.
@@ -90,7 +90,8 @@
     static DefaultCitation metadataWithHierarchy() {
         final DefaultCitation citation = TreeNodeChildrenTest.metadataWithMultiOccurrences();
         AbstractParty party = new DefaultOrganisation("Some organisation", null, null, null);
-        DefaultResponsibility responsibility = new DefaultResponsibility(Role.DISTRIBUTOR, null, party);
+        DefaultResponsibleParty responsibility = new DefaultResponsibleParty(Role.DISTRIBUTOR);
+        responsibility.setParties(Set.of(party));
         assertTrue(citation.getCitedResponsibleParties().add(responsibility));
 
         // Add a second responsible party with deeper hierarchy.
@@ -99,7 +100,8 @@
         address.setElectronicMailAddresses(Set.of("Some email"));
         contact.setAddresses(Set.of(address));
         party = new DefaultIndividual("Some person of contact", null, contact);
-        responsibility = new DefaultResponsibility(Role.POINT_OF_CONTACT, null, party);
+        responsibility = new DefaultResponsibleParty(Role.POINT_OF_CONTACT);
+        responsibility.setParties(Set.of(party));
         assertTrue(citation.getCitedResponsibleParties().add(responsibility));
         return citation;
     }
@@ -181,7 +183,7 @@
      *
      * <p>If {@link #valuePolicy} is {@link ValueExistencePolicy#COMPACT}, then this method removes the elements at
      * indices 0, 6 and 10 (if {@code offset} = 0) or 1, 7 and 11 (if {@code offset} = 1) from the {@code expected}
-     * array before to perform the comparison.</p>
+     * array before to perform the comparison (note: actual indices vary according branches).</p>
      *
      * @param  offset    0 if compact mode excludes the parent, or 1 if compact mode exclude the first child.
      * @param  column    the column from which to get a value.
@@ -192,8 +194,8 @@
     private void assertCitationContentEquals(final int offset, final TableColumn<?> column, final Object... expected) {
         if (valuePolicy == ValueExistencePolicy.COMPACT) {
             assertEquals(19, expected.length);
-            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, 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,  1+offset, expected,    offset, 16-offset);    // Compact the "Title" element.
             Arrays.fill(expected, 16, 19, null);
         }
@@ -212,16 +214,16 @@
               "Alternate title (2 of 2)",
               "Edition",
               "Cited responsible party (1 of 2)",
-                "Role",
-                "Organisation",                         // A Party subtype
+                "Organisation",
                   "Name",                               // In COMPACT mode, this value is associated to "Organisation" node.
-              "Cited responsible party (2 of 2)",
                 "Role",
-                "Individual",                           // A Party subtype
+              "Cited responsible party (2 of 2)",
+                "Individual",
                   "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");
@@ -242,16 +244,16 @@
               "alternateTitle",
               "edition",
               "citedResponsibleParty",
-                "role",
                 "party",
                   "name",                               // In COMPACT mode, this value is associated to "party" node.
-              "citedResponsibleParty",
                 "role",
+              "citedResponsibleParty",
                 "party",
                   "name",                               // In COMPACT mode, this value is associated to "party" node.
                   "contactInfo",
                     "address",
                       "electronicMailAddress",
+                "role",
               "presentationForm",
               "presentationForm",
               "otherCitationDetails");
@@ -264,6 +266,7 @@
     public void testGetIndex() {
         final Integer ZERO = 0;
         final Integer ONE  = 1;
+        skipCountCheck = true;                              // Because of the null value at the end of following array.
         assertCitationContentEquals(1, TableColumn.INDEX,
             null,           // CI_Citation
               null,         // title
@@ -271,19 +274,19 @@
               ONE,          // alternateTitle
               null,         // edition
               ZERO,         // citedResponsibleParty
-                null,       // role
                 ZERO,       // party (organisation)
                   null,     // name                         — in COMPACT mode, this value is associated to "party" node.
-              ONE,          // citedResponsibleParty
                 null,       // role
+              ONE,          // citedResponsibleParty
                 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
-              ZERO);        // otherCitationDetails
+              null);        // otherCitationDetails
     }
 
     /**
@@ -297,17 +300,17 @@
               InternationalString.class,
               InternationalString.class,
               InternationalString.class,
-              Responsibility.class,
-                Role.class,
+              ResponsibleParty.class,
                 Party.class,                            // In COMPACT mode, value with be the one of "name" node instead.
                   InternationalString.class,            // Name
-              Responsibility.class,
                 Role.class,
+              ResponsibleParty.class,
                 Party.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);
@@ -325,16 +328,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");
@@ -379,6 +382,12 @@
     }
 
     /**
+     * For disabling the check of child nodes count.
+     * This hack is specific to the branch using GeoAPI 3.0 (not needed on the branch using GeoAPI 4.0).
+     */
+    private boolean skipCountCheck;
+
+    /**
      * Compares the result of the given getter method invoked on the given node, then invoked
      * on all children of that given. In the particular case of the {@link TableColumn#NAME},
      * international strings are replaced by unlocalized strings before comparisons.
@@ -396,6 +405,7 @@
         if (valuePolicy == ValueExistencePolicy.COMPACT) {
             while (expected[count-1] == null) count--;
         }
+        if (skipCountCheck) return;
         assertEquals(count, assertColumnContentEquals(node, column, expected, 0),
                      "Missing values in the tested metadata.");
     }
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 ce8f2b0..3a76924 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
@@ -37,8 +37,8 @@
 import org.apache.sis.test.TestCase;
 import static org.apache.sis.test.Assertions.assertMultilinesEquals;
 
-// Specific to the geoapi-4.0 branch:
-import org.apache.sis.metadata.iso.citation.DefaultResponsibility;
+// Specific to the main and geoapi-3.1 branches:
+import org.apache.sis.metadata.iso.citation.DefaultResponsibleParty;
 
 
 /**
@@ -84,10 +84,9 @@
             "Citation……………………………………………………………………………… Undercurrent\n" +
             "  ├─Alternate title………………………………………………… Andākarento\n" +
             "  ├─Cited responsible party (1 of 2)\n" +
-            "  │   ├─Role…………………………………………………………………… Author\n" +
-            "  │   └─Individual…………………………………………………… Testsuya Toyoda\n" +
+            "  │   ├─Individual…………………………………………………… Testsuya Toyoda\n" +
+            "  │   └─Role…………………………………………………………………… Author\n" +
             "  ├─Cited responsible party (2 of 2)\n" +
-            "  │   ├─Role…………………………………………………………………… Editor\n" +
             "  │   ├─Extent……………………………………………………………… World\n" +
             "  │   │   └─Geographic element\n" +
             "  │   │       ├─West bound longitude…… 180°W\n" +
@@ -95,7 +94,8 @@
             "  │   │       ├─South bound latitude…… 90°S\n" +
             "  │   │       ├─North bound latitude…… 90°N\n" +
             "  │   │       └─Extent type code……………… True\n" +
-            "  │   └─Organisation……………………………………………… Kōdansha\n" +
+            "  │   ├─Organisation……………………………………………… Kōdansha\n" +
+            "  │   └─Role…………………………………………………………………… Editor\n" +
             "  ├─Presentation form (1 of 2)…………………… Document digital\n" +
             "  ├─Presentation form (2 of 2)…………………… Document hardcopy\n" +
             "  └─ISBN……………………………………………………………………………… 9782505004509\n", text);
@@ -111,7 +111,7 @@
         final DefaultCitation untitled = new DefaultCitation();
         titled  .setPresentationForms(Set.of(PresentationForm.DOCUMENT_HARDCOPY));
         coded   .setPresentationForms(Set.of(PresentationForm.IMAGE_HARDCOPY));
-        untitled.setCitedResponsibleParties(Set.of(new DefaultResponsibility(Role.AUTHOR, null, null)));
+        untitled.setCitedResponsibleParties(Set.of(new DefaultResponsibleParty(Role.AUTHOR)));
         final DefaultProcessing processing = new DefaultProcessing();
         processing.setDocumentations(List.of(titled, coded, untitled));
         final String text = format.format(processing.asTreeTable());
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 6fd4062..8b3e026 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" +
-            "  │   ├─Role……………………………………………………………………………………… Distributor\n" +
-            "  │   └─Organisation………………………………………………………………… Some organisation\n" +
+            "  │   ├─Organisation………………………………………………………………… Some organisation\n" +
+            "  │   └─Role……………………………………………………………………………………… Distributor\n" +
             "  ├─Cited responsible party (2 of 2)\n" +
-            "  │   ├─Role……………………………………………………………………………………… Point of contact\n" +
-            "  │   └─Individual……………………………………………………………………… Some person of contact\n" +
-            "  │       └─Contact info\n" +
-            "  │           └─Address\n" +
-            "  │               └─Electronic mail address…… Some email\n" +
+            "  │   ├─Individual……………………………………………………………………… Some person of contact\n" +
+            "  │   │   └─Contact info\n" +
+            "  │   │       └─Address\n" +
+            "  │   │           └─Electronic mail address…… Some email\n" +
+            "  │   └─Role……………………………………………………………………………………… Point of contact\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/TypeMapTest.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/TypeMapTest.java
index 9410f74..9b5f510 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/TypeMapTest.java
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/TypeMapTest.java
@@ -39,8 +39,8 @@
 import static org.junit.jupiter.api.Assertions.*;
 import org.apache.sis.test.TestCase;
 
-// Specific to the geoapi-4.0 branch:
-import org.opengis.metadata.citation.Responsibility;
+// Specific to the main and geoapi-3.1 branches:
+import org.opengis.metadata.citation.ResponsibleParty;
 
 
 /**
@@ -74,7 +74,7 @@
             new SimpleEntry<>("edition",               InternationalString.class),
             new SimpleEntry<>("editionDate",           Date.class),
             new SimpleEntry<>("identifier",            Identifier.class),
-            new SimpleEntry<>("citedResponsibleParty", Responsibility.class),
+            new SimpleEntry<>("citedResponsibleParty", ResponsibleParty.class),
             new SimpleEntry<>("presentationForm",      PresentationForm.class),
             new SimpleEntry<>("series",                Series.class),
             new SimpleEntry<>("otherCitationDetails",  InternationalString.class),
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/ValueMapTest.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/ValueMapTest.java
index 7007417..b000745 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/ValueMapTest.java
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/ValueMapTest.java
@@ -35,8 +35,8 @@
 import org.apache.sis.test.TestCase;
 import static org.apache.sis.test.TestUtilities.getSingleton;
 
-// Specific to the geoapi-4.0 branch:
-import org.apache.sis.metadata.iso.citation.DefaultResponsibility;
+// Specific to the main and geoapi-3.1 branches:
+import org.apache.sis.metadata.iso.citation.DefaultResponsibleParty;
 
 
 /**
@@ -62,7 +62,7 @@
     /**
      * The author of the metadata instance created by {@link #createCitation()}.
      */
-    private DefaultResponsibility author;
+    private DefaultResponsibleParty author;
 
     /**
      * Creates a new test case.
@@ -90,7 +90,7 @@
      */
     private Map<String,Object> createCitation() {
         title    = new SimpleInternationalString("Undercurrent");
-        author   = new DefaultResponsibility();
+        author   = new DefaultResponsibleParty();
         citation = new DefaultCitation(title);
         author.setParties(Set.of(new DefaultIndividual("Testsuya Toyoda", null, null)));
         citation.setCitedResponsibleParties(Set.of(author));
@@ -167,7 +167,6 @@
             new SimpleEntry<>("identifiers",             citation.getIdentifiers()),
             new SimpleEntry<>("citedResponsibleParties", List.of(author)),
             new SimpleEntry<>("presentationForms",       Set.of()),
-            new SimpleEntry<>("otherCitationDetails",    List.of()),
             new SimpleEntry<>("ISBN",                    "9782505004509"),
             new SimpleEntry<>("onlineResources",         List.of()),
             new SimpleEntry<>("graphics",                List.of())
@@ -197,7 +196,6 @@
             new SimpleEntry<>("identifiers",             citation.getIdentifiers()),
             new SimpleEntry<>("citedResponsibleParties", List.of(author)),
             new SimpleEntry<>("presentationForms",       Set.of()),
-            new SimpleEntry<>("otherCitationDetails",    List.of()),
             new SimpleEntry<>("ISBN",                    "9782505004509"),
             new SimpleEntry<>("onlineResources",         List.of()),
             new SimpleEntry<>("graphics",                List.of())
@@ -227,7 +225,7 @@
             new SimpleEntry<>("citedResponsibleParties", List.of(author)),
             new SimpleEntry<>("presentationForms",       Set.of()),
             new SimpleEntry<>("series",                  null),
-            new SimpleEntry<>("otherCitationDetails",    List.of()),
+            new SimpleEntry<>("otherCitationDetails",    null),
 //          new SimpleEntry<>("collectiveTitle",         null),  -- deprecated as of ISO 19115:2014.
             new SimpleEntry<>("ISBN",                    "9782505004509"),
             new SimpleEntry<>("ISSN",                    null),
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 4bc4676..db175ce 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
@@ -96,7 +96,7 @@
             org.opengis.metadata.content.FeatureCatalogueDescription.class,
             org.opengis.metadata.content.ImageDescription.class,
             org.opengis.metadata.content.ImagingCondition.class,
-            org.opengis.metadata.content.PolarisationOrientation.class,
+            org.opengis.metadata.content.PolarizationOrientation.class,
             org.opengis.metadata.content.RangeDimension.class,
             org.opengis.metadata.content.RangeElementDescription.class,
             org.opengis.metadata.content.SampleDimension.class,
@@ -108,7 +108,7 @@
             org.opengis.metadata.distribution.Format.class,
             org.opengis.metadata.distribution.Medium.class,
             org.opengis.metadata.distribution.MediumFormat.class,
-            org.apache.sis.metadata.iso.legacy.MediumName.class,
+            org.opengis.metadata.distribution.MediumName.class,
             org.opengis.metadata.distribution.StandardOrderProcess.class,
             org.opengis.metadata.extent.BoundingPolygon.class,
             org.opengis.metadata.extent.Extent.class,
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/MarshallingTest.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/MarshallingTest.java
deleted file mode 100644
index 2dc96be..0000000
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/MarshallingTest.java
+++ /dev/null
@@ -1,659 +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.iso;
-
-import java.util.Date;
-import java.util.Locale;
-import java.util.List;
-import java.util.Set;
-import java.util.Map;
-import java.util.Collection;
-import java.util.LinkedHashMap;
-import java.util.MissingResourceException;
-import java.util.logging.Filter;
-import java.util.logging.LogRecord;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.io.StringWriter;
-import java.nio.charset.Charset;
-import java.nio.charset.StandardCharsets;
-import jakarta.xml.bind.Marshaller;
-import jakarta.xml.bind.JAXBException;
-import org.opengis.annotation.Obligation;
-import org.opengis.util.RecordType;
-import org.opengis.metadata.Datatype;
-import org.opengis.metadata.citation.*;
-import org.opengis.metadata.constraint.*;
-import org.opengis.metadata.content.*;
-import org.opengis.metadata.extent.*;
-import org.opengis.metadata.identification.*;
-import org.opengis.metadata.maintenance.*;
-import org.opengis.metadata.spatial.*;
-import org.opengis.geometry.primitive.Point;
-import org.apache.sis.metadata.iso.citation.*;
-import org.apache.sis.metadata.iso.constraint.*;
-import org.apache.sis.metadata.iso.content.*;
-import org.apache.sis.metadata.iso.distribution.*;
-import org.apache.sis.metadata.iso.extent.*;
-import org.apache.sis.metadata.iso.identification.*;
-import org.apache.sis.metadata.iso.maintenance.*;
-import org.apache.sis.metadata.iso.spatial.*;
-import org.apache.sis.util.SimpleInternationalString;
-import org.apache.sis.util.DefaultInternationalString;
-import org.apache.sis.util.iso.DefaultRecordSchema;
-import org.apache.sis.util.iso.Names;
-import org.apache.sis.measure.Units;
-import org.apache.sis.xml.XML;
-import org.apache.sis.xml.NilReason;
-import org.apache.sis.xml.MarshallerPool;
-import org.apache.sis.xml.IdentifierSpace;
-import org.apache.sis.xml.bind.gcx.Anchor;
-import org.apache.sis.xml.bind.metadata.replace.ReferenceSystemMetadata;
-
-// Test dependencies
-import org.junit.jupiter.api.Test;
-import static org.junit.jupiter.api.Assertions.*;
-import org.apache.sis.metadata.xml.TestUsingFile;
-
-
-/**
- * Simple test cases for marshalling a {@link DefaultMetadata} object to an XML file.
- * This class is used to test the ISO 19115-3 metadata standard implementation.
- *
- * @author  Cullen Rombach (Image Matters)
- * @author  Martin Desruisseaux (Geomatys)
- *
- * @see <a href="https://issues.apache.org/jira/browse/SIS-400">SIS-400</a>
- */
-public final class MarshallingTest extends TestUsingFile implements Filter {
-    /**
-     * The marshaller used to handle marshalling the created DefaultMetadata object.
-     */
-    private final Marshaller marshaller;
-
-    /**
-     * The pool from which the marshaller is pulled.
-     */
-    private final MarshallerPool pool;
-
-    /**
-     * The output to which the metadata object will be marshalled.
-     */
-    private final StringWriter output;
-
-    /**
-     * {@code true} if marshalling legacy XML instead of latest schema.
-     */
-    private boolean legacyXML;
-
-    /**
-     * Initializes a new test case.
-     *
-     * @throws JAXBException if an error occurred while preparing the marshaller.
-     */
-    public MarshallingTest() throws JAXBException {
-        output     = new StringWriter();
-        pool       = getMarshallerPool();
-        marshaller = pool.acquireMarshaller();
-        marshaller.setProperty(XML.WARNING_FILTER, this);
-    }
-
-    /**
-     * Creates a metadata object to marshal.
-     */
-    @SuppressWarnings("deprecation")
-    private static DefaultMetadata metadata() throws URISyntaxException {
-        /*
-         * Metadata
-         *   ├─Metadata identifier…… a-metadata-identifier
-         *   │   └─Code space………………… md.id.ns
-         *   ├─Parent metadata……………… A parent metadata
-         *   │   └─Identifier………………… a-parent-identifier
-         *   │       └─Code space……… pmd.id.ns
-         *   ├─Language (1 de 2)………… English
-         *   ├─Language (2 de 2)………… French (Canada)
-         *   ├─Character set…………………… ISO-8859-1
-         *   └─Metadata scope
-         *       ├─Resource scope……… Dataset
-         *       └─Name………………………………… Metadata for an imaginary data set
-         *
-         * Some code are indented for readability and more local variable scopes.
-         */
-        final DefaultMetadata md = new DefaultMetadata();
-        {
-            // Metadata identifier
-            final DefaultIdentifier id = new DefaultIdentifier("a-metadata-identifier");
-            id.setCodeSpace("md.id.ns");
-            md.setMetadataIdentifier(id);
-        }
-        final Map<Locale,Charset> languages = Map.of(
-                Locale.ENGLISH,       StandardCharsets.ISO_8859_1,
-                Locale.CANADA_FRENCH, StandardCharsets.ISO_8859_1);
-        md.setLocalesAndCharsets(languages);
-        {
-            // Parent metadata
-            final DefaultCitation parent = new DefaultCitation("A parent metadata");
-            final DefaultIdentifier parentId = new DefaultIdentifier("a-parent-identifier");
-            parentId.setCodeSpace("pmd.id.ns");
-            parent.getIdentifiers().add(parentId);
-            md.setParentMetadata(parent);
-        }
-        // mdb:metadataScope (hierarchyLevel and hierarchyLevelName in legacy ISO 19115:2003 model)
-        md.getMetadataScopes().add(new DefaultMetadataScope(ScopeCode.DATASET, "Metadata for an imaginary data set"));
-        final DefaultOnlineResource onlineResource;
-        {
-            /*
-             * Contact information for the parties.
-             *
-             * Organisation………………………………………………………………… Plato Republic
-             *   ├─Contact info
-             *   │   ├─Phone (1 de 2)
-             *   │   │   ├─Number………………………………………………… 555-444-3333
-             *   │   │   └─Number type…………………………………… Voice
-             *   │   ├─Phone (2 de 2)
-             *   │   │   ├─Number………………………………………………… 555-555-5555
-             *   │   │   └─Number type…………………………………… Facsimile
-             *   │   ├─Address
-             *   │   │   ├─Delivery point…………………………… 123 Main Street
-             *   │   │   ├─City……………………………………………………… Metropolis city
-             *   │   │   ├─Administrative area……………… Utopia province
-             *   │   │   ├─Postal code…………………………………… A1A 2C2
-             *   │   │   ├─Country……………………………………………… Atlantis island
-             *   │   │   └─Electronic mail address…… test@example.com
-             *   │   ├─Online resource
-             *   │   │   ├─Linkage……………………………………………… http://example.com
-             *   │   │   ├─Protocol…………………………………………… Submarine HTTP
-             *   │   │   ├─Application profile……………… Imaginary work
-             *   │   │   ├─Name……………………………………………………… Timaeus & Critias
-             *   │   │   ├─Description…………………………………… A dialog between philosophers.
-             *   │   │   └─Function…………………………………………… Search
-             *   │   ├─Hours of service………………………………… Weekdays 9:00 AM - 5:00 PM
-             *   │   ├─Contact instructions……………………… Through thought
-             *   │   └─Contact type…………………………………………… Virtual
-             *   └─Individual…………………………………………………………… Socrates
-             *       └─Position name………………………………………… Philosopher
-             */
-            final DefaultContact contact = new DefaultContact();
-            contact.setPhones(List.of(new DefaultTelephone("555-444-3333", TelephoneType.VOICE),
-                                      new DefaultTelephone("555-555-5555", TelephoneType.FACSIMILE)));
-            {
-                {
-                    // Address information
-                    final DefaultAddress address = new DefaultAddress();
-                    address.setDeliveryPoints(Set.of(new SimpleInternationalString("123 Main Street")));
-                    address.getElectronicMailAddresses().add("test@example.com");
-                    address.setCity(new SimpleInternationalString("Metropolis city"));
-                    address.setAdministrativeArea(new SimpleInternationalString("Utopia province"));
-                    address.setPostalCode("A1A 2C2");
-                    address.setCountry(new SimpleInternationalString("Atlantis island"));
-                    contact.getAddresses().add(address);
-                }
-                // Online resources
-                final DefaultInternationalString description = new DefaultInternationalString();
-                description.add(Locale.ENGLISH, "A dialog between philosophers.");
-                description.add(Locale.FRENCH,  "Un dialogue entre philosophes.");
-                onlineResource = new DefaultOnlineResource(new URI("http://example.com"));
-                onlineResource.setName(new SimpleInternationalString("Timaeus & Critias"));
-                onlineResource.setDescription(description);
-                onlineResource.setProtocol("Submarine HTTP");
-                onlineResource.setApplicationProfile("Imaginary work");
-                onlineResource.setFunction(OnLineFunction.SEARCH);
-                onlineResource.getIdentifierMap().putSpecialized(IdentifierSpace.ID, "timaeus");    // For enabling references
-                contact.getOnlineResources().add(onlineResource);
-                contact.setHoursOfService(Set.of(new SimpleInternationalString("Weekdays 9:00 AM - 5:00 PM")));
-                contact.setContactInstructions(new SimpleInternationalString("Through thought"));
-                contact.setContactType(new SimpleInternationalString("Virtual"));
-                contact.getIdentifierMap().putSpecialized(IdentifierSpace.ID, "thought");           // For enabling references
-            }
-            // Create some individuals
-            final DefaultIndividual individual  = new DefaultIndividual("Socrates", "Philosopher", null);
-            final DefaultIndividual individual2 = new DefaultIndividual("Hermocrates", "Politician", contact);
-            final DefaultOrganisation org = new DefaultOrganisation("Plato Republic", null, individual, contact);
-            md.setContacts(List.of(new DefaultResponsibility(Role.POINT_OF_CONTACT, null, org),
-                                   new DefaultResponsibility(Role.POINT_OF_CONTACT, null, individual2)));
-        }
-        // Date info (date stamp in legacy ISO 19115:2003 model)
-        final Collection<CitationDate> dateInfo = Set.of(new DefaultCitationDate(new Date(1260961229580L), DateType.CREATION));
-        md.setDateInfo(dateInfo);
-        {
-            // Metadata standard
-            final DefaultCitation standard = new DefaultCitation("ISO 19115-1");
-            standard.setEdition(new SimpleInternationalString("2014"));
-            md.getMetadataStandards().add(standard);
-        }
-        {
-            /*
-             * Spatial representation info : Georectified
-             *   ├─Number of dimensions………………………………………………… 2
-             *   ├─Axis dimension properties (1 de 2)…………… Row
-             *   │   ├─Dimension size……………………………………………………… 7 777
-             *   │   └─Resolution………………………………………………………………… 10
-             *   ├─Axis dimension properties (2 de 2)…………… Column
-             *   │   ├─Dimension size……………………………………………………… 2 233
-             *   │   └─Resolution………………………………………………………………… 5
-             *   ├─Cell geometry…………………………………………………………………… Area
-             *   ├─Transformation parameter availability…… false
-             *   ├─Check point availability……………………………………… false
-             *   └─Point in pixel………………………………………………………………… Upper right
-             */
-            final DefaultGeorectified georectified = new DefaultGeorectified();
-            georectified.setNumberOfDimensions(2);
-            final DefaultDimension rows = new DefaultDimension(DimensionNameType.ROW,    7777);
-            final DefaultDimension cols = new DefaultDimension(DimensionNameType.COLUMN, 2233);
-            rows.setResolution(10.0);
-            cols.setResolution( 5.0);
-            georectified.setAxisDimensionProperties(List.of(rows, cols));
-            georectified.setCellGeometry(CellGeometry.AREA);
-            georectified.setPointInPixel(PixelOrientation.UPPER_RIGHT);
-            georectified.getCornerPoints().add(NilReason.MISSING.createNilObject(Point.class));
-            md.getSpatialRepresentationInfo().add(georectified);
-        }
-        {
-            // Reference System Information
-            final ReferenceSystemMetadata refSystem = new ReferenceSystemMetadata();
-            final DefaultCitation cit = new DefaultCitation("Atlantis grid");
-            cit.setDates(dateInfo);
-            {
-                //  Responsibilities
-                final DefaultOrganisation org = new DefaultOrganisation();
-                org.setName(new SimpleInternationalString("Atlantis national mapping agency"));
-                cit.getCitedResponsibleParties().add(new DefaultResponsibility(Role.PUBLISHER, null, org));
-            }
-            // Identifier
-            final DefaultIdentifier id = new DefaultIdentifier("AG9000");
-            id.setAuthority(cit);
-            id.setCodeSpace("rs.id.ns");
-            id.setVersion("1.0");
-            id.setDescription(new SimpleInternationalString("An imaginary reference system."));
-            refSystem.setName(id);
-            md.getReferenceSystemInfo().add(refSystem);
-        }
-        {
-            /*
-             * Extended element information…… ExtendedElementName
-             *   ├─Parent entity………………………………… VirtualObject
-             *   ├─Definition………………………………………… An extended element not included in the standard.
-             *   ├─Obligation………………………………………… Conditional
-             *   ├─Condition…………………………………………… Presents in “Imaginary work” profile.
-             *   ├─Data type…………………………………………… Meta class
-             *   ├─Maximum occurrence…………………… 3
-             *   ├─Domain value…………………………………… Alpha, beta or gamma.
-             *   ├─Rule………………………………………………………… Element exists in cited resource.
-             *   └─Rationale…………………………………………… For testing extended elements.
-             */
-            final DefaultMetadataExtensionInformation extension = new DefaultMetadataExtensionInformation();
-            extension.setExtensionOnLineResource(onlineResource);
-            final DefaultExtendedElementInformation elementInfo = new DefaultExtendedElementInformation();
-            elementInfo.setName("ExtendedElementName");
-            elementInfo.setDefinition(new SimpleInternationalString("An extended element not included in the standard."));
-            elementInfo.setObligation(Obligation.CONDITIONAL);
-            elementInfo.setCondition(new SimpleInternationalString("Presents in “Imaginary work” profile."));
-            elementInfo.setDataType(Datatype.META_CLASS);
-            elementInfo.setMaximumOccurrence(3);
-            elementInfo.setDomainValue(new SimpleInternationalString("Alpha, beta or gamma."));
-            elementInfo.setShortName("ExtEltName");
-            elementInfo.setDomainCode(1234);
-            elementInfo.setParentEntity(Set.of("VirtualObject"));
-            elementInfo.setRule(new SimpleInternationalString("Element exists in cited resource."));
-            elementInfo.setRationale(new SimpleInternationalString("For testing extended elements."));
-            elementInfo.getSources().add(NilReason.valueOf("other:test").createNilObject(Responsibility.class));
-            extension.getExtendedElementInformation().add(elementInfo);
-            md.getMetadataExtensionInfo().add(extension);
-        }
-        /*
-         * Data identification info
-         *   ├─Abstract………………… Méta-données pour une carte imaginaire.
-         *   └─Purpose…………………… For XML (un)marshalling tests.
-         */
-        final DefaultDataIdentification dataId = new DefaultDataIdentification();
-        {
-            final DefaultInternationalString description = new DefaultInternationalString();
-            description.add(Locale.ENGLISH, "Metadata for an imaginary map.");
-            description.add(Locale.FRENCH,  "Méta-données pour une carte imaginaire.");
-            dataId.setAbstract(description);
-            dataId.setPurpose(new SimpleInternationalString("For XML (un)marshalling tests."));
-        }
-        final Collection<Extent> extents;
-        {
-            /*
-             * Extent……………………………………………………………… Azores
-             *   ├─Geographic element
-             *   │   ├─West bound longitude…… 24°30′W
-             *   │   ├─East bound longitude…… 32°W
-             *   │   ├─South bound latitude…… 36°45′N
-             *   │   ├─North bound latitude…… 40°N
-             *   │   └─Extent type code……………… true
-             *   └─Temporal element
-             */
-            final DefaultExtent extent = new DefaultExtent();
-            extent.setDescription(new SimpleInternationalString("Azores"));
-            {
-                final DefaultGeographicBoundingBox bbox = new DefaultGeographicBoundingBox();
-                bbox.setInclusion(true);
-                bbox.setNorthBoundLatitude( 40.00);
-                bbox.setEastBoundLongitude(-32.00);
-                bbox.setSouthBoundLatitude( 36.75);
-                bbox.setWestBoundLongitude(-24.50);
-                extent.getGeographicElements().add(bbox);
-            }
-            final DefaultTemporalExtent temporal = new DefaultTemporalExtent();
-            extent.getTemporalElements().add(temporal);
-            extent.getIdentifierMap().putSpecialized(IdentifierSpace.ID, "azores");     // For enabling references
-            extents = Set.of(extent);
-            dataId.setExtents(extents);
-        }
-        final Collection<Constraints> resourceConstraints;
-        {
-            /*
-             * Constraints
-             *   ├─Use limitation…………………………………… Not for navigation.
-             *   ├─Constraint application scope
-             *   │   └─Level………………………………………………… Document
-             *   ├─Graphic
-             *   │   ├─File name……………………………………… ocean.png
-             *   │   ├─File description…………………… Somewhere in the Atlantic ocean
-             *   │   ├─File type……………………………………… PNG image
-             *   │   ├─Linkage
-             *   │   └─Image constraints
-             *   └─Releasability
-             *       └─Statement……………………………………… Public domain
-             */
-            final DefaultConstraints constraint = new DefaultConstraints();
-            final DefaultBrowseGraphic graphic = new DefaultBrowseGraphic(new URI("ocean.png"));
-            graphic.setFileDescription(new SimpleInternationalString("Somewhere in the Atlantic ocean"));
-            graphic.setFileType("PNG image");
-            graphic.getImageConstraints().add(new DefaultConstraints());
-            graphic.getLinkages().add(new DefaultOnlineResource());
-            constraint.getGraphics().add(graphic);
-            constraint.setUseLimitations(Set.of(new SimpleInternationalString("Not for navigation.")));
-
-            // Releasability
-            final DefaultReleasability releasability = new DefaultReleasability();
-            releasability.setStatement(new SimpleInternationalString("Public domain"));
-            constraint.setReleasability(releasability);
-            constraint.setConstraintApplicationScope(new DefaultScope(ScopeCode.DOCUMENT));
-            constraint.getIdentifierMap().putSpecialized(IdentifierSpace.ID, "public");         // For enabling references
-            resourceConstraints = Set.of(constraint);
-            dataId.setResourceConstraints(resourceConstraints);
-        }
-        dataId.getSpatialRepresentationTypes().add(SpatialRepresentationType.GRID);
-        {
-            // Spatial resolution
-            final DefaultResolution resolution = new DefaultResolution();
-            resolution.setDistance(56777.0);
-            dataId.getSpatialResolutions().add(resolution);
-        }
-        dataId.setTopicCategories(List.of(TopicCategory.OCEANS, TopicCategory.SOCIETY));
-        dataId.getStatus().add(Progress.HISTORICAL_ARCHIVE);
-        /*
-         * Citation………………………………………………………… A lost island
-         *   ├─Alternate title (1 de 2)…… Island lost again
-         *   ├─Alternate title (2 de 2)…… Map example
-         *   ├─Date………………………………………………………… 2018-04-09 00:00:00
-         *   │   └─Date type………………………………… Création
-         *   ├─Edition………………………………………………… First edition
-         *   └─Edition date…………………………………… 2018-04-10 00:00:00
-         */
-        final DefaultCitation cit = new DefaultCitation();
-        cit.setTitle(new SimpleInternationalString("A lost island"));
-        cit.setEdition(new SimpleInternationalString("First edition"));
-        cit.setEditionDate(new Date(1523311200000L));
-        cit.setCollectiveTitle(new SimpleInternationalString("Popular legends"));
-        cit.setAlternateTitles(List.of(new SimpleInternationalString("Island lost again"),
-                                       new Anchor(new URI("http://map-example.com"), "Map example")));
-        cit.getDates().add(new DefaultCitationDate(new Date(1523224800000L), DateType.CREATION));
-        cit.getIdentifierMap().putSpecialized(IdentifierSpace.ID, "lost-island");
-        dataId.setCitation(cit);
-        dataId.setTemporalResolutions(Set.of());          // TODO: need a more complete sis-temporal.
-        final Collection<MaintenanceInformation> resourceMaintenances;
-        {
-            /*
-             * Maintenance information
-             *   ├─Maintenance and update frequency…… Not planned
-             *   ├─Maintenance date……………………………………………… 3000-01-01 00:00:00
-             *   │   └─Date type……………………………………………………… Révision
-             *   └─Maintenance scope
-             *       ├─Level………………………………………………………………… Model
-             *       └─Level description
-             *           └─Dataset………………………………………………… Imaginary map
-             */
-            DefaultMaintenanceInformation maintenanceInfo = new DefaultMaintenanceInformation();
-            maintenanceInfo.setMaintenanceAndUpdateFrequency(MaintenanceFrequency.NOT_PLANNED);
-            maintenanceInfo.getMaintenanceDates().add(new DefaultCitationDate(new Date(32503676400000L), DateType.REVISION));
-            final DefaultScope maintenanceScope = new DefaultScope();
-            maintenanceScope.setLevel(ScopeCode.MODEL);
-            {
-                // Scope level descriptions
-                final DefaultScopeDescription scopeDescription = new DefaultScopeDescription();
-                scopeDescription.setDataset("Imaginary map");
-                maintenanceScope.getLevelDescription().add(scopeDescription);
-            }
-            maintenanceInfo.getIdentifierMap().putSpecialized(IdentifierSpace.ID, "not-planned");
-            maintenanceInfo.getMaintenanceScopes().add(maintenanceScope);
-            resourceMaintenances = Set.of(maintenanceInfo);
-            dataId.setResourceMaintenances(resourceMaintenances);
-        }
-        {
-            /*
-             * Format
-             *   ├─Format specification citation…… Portable Network Graphics
-             *   │   ├─Alternate title……………………………… PNG
-             *   │   └─Edition…………………………………………………… November 2003
-             *   ├─Amendment number……………………………………… Second edition
-             *   └─File decompression technique……… L77 / Huffman coding
-             */
-            final DefaultFormat resourceFormat = new DefaultFormat();
-            resourceFormat.setName(new SimpleInternationalString("PNG"));
-            resourceFormat.setSpecification(new SimpleInternationalString("Portable Network Graphics"));
-            resourceFormat.setAmendmentNumber(new SimpleInternationalString("Second edition"));
-            resourceFormat.setVersion(new SimpleInternationalString("November 2003"));
-            resourceFormat.setFileDecompressionTechnique(new SimpleInternationalString("L77 / Huffman coding"));
-            dataId.getResourceFormats().add(resourceFormat);
-        }
-        final Collection<Keywords> descriptiveKeywords;
-        {
-            /*
-             * Keywords
-             *   ├─Thesaurus name………… Plato's dialogues
-             *   ├─Keyword class…………… Greek elements
-             *   ├─Keyword (1 de 2)…… Water
-             *   ├─Keyword (2 de 2)…… Aether
-             *   └─Type…………………………………… Theme
-             */
-            final DefaultKeywords keywords = new DefaultKeywords();
-            keywords.setType(KeywordType.THEME);
-            keywords.setThesaurusName(new DefaultCitation("Plato's dialogues"));
-            final DefaultKeywordClass keywordClass = new DefaultKeywordClass();
-            keywordClass.setClassName(new SimpleInternationalString("Greek elements"));
-            keywords.setKeywordClass(keywordClass);
-            keywords.setKeywords(List.of(new SimpleInternationalString("Water"),
-                                         new SimpleInternationalString("Aether")));
-            keywords.getIdentifierMap().putSpecialized(IdentifierSpace.ID, "greek-elements");
-            descriptiveKeywords = Set.of(keywords);
-            dataId.setDescriptiveKeywords(descriptiveKeywords);
-        }
-        {
-            /*
-             * Usage………………………………………………………………………… For testing purpose only.
-             *   ├─Usage date time…………………………………… 2018-04-10 14:00:00
-             *   ├─User determined limitations…… Not to be used outside MarshallingTest.java test file.
-             *   └─Response……………………………………………………… Random elements
-             */
-            final DefaultUsage usage = new DefaultUsage();
-            usage.setSpecificUsage(new SimpleInternationalString("For testing purpose only."));
-            usage.setUsageDate(new Date(1523361600000L));
-            usage.setResponses(Set.of(new SimpleInternationalString("Random elements")));
-            usage.setUserDeterminedLimitations(new SimpleInternationalString("Not to be used outside MarshallingTest.java test file."));
-            dataId.getResourceSpecificUsages().add(usage);
-        }
-        final Collection<AssociatedResource> associatedResources;
-        {
-            // Associated resources (AggregationInfo in 19139)
-            final DefaultAssociatedResource associatedResource = new DefaultAssociatedResource();
-            associatedResource.setAssociationType(AssociationType.DEPENDENCY);
-            associatedResource.setInitiativeType(InitiativeType.EXPERIMENT);
-            associatedResource.getIdentifierMap().putSpecialized(IdentifierSpace.ID, "dependency");
-            associatedResources = Set.of(associatedResource);
-            dataId.setAssociatedResources(associatedResources);
-        }
-        dataId.setLocalesAndCharsets(languages);     // Locales (ISO 19115:2014) a.k.a Languages and CharacterSets (ISO 19115:2003)
-        dataId.setEnvironmentDescription (new SimpleInternationalString("High humidity."));
-        dataId.setSupplementalInformation(new SimpleInternationalString("High water pressure."));
-        {
-            // Service identification info
-            final DefaultServiceIdentification serviceId = new DefaultServiceIdentification();
-            serviceId.setCitation(cit);
-            serviceId.setAbstract(new SimpleInternationalString("An inspiration for story tellers."));
-            serviceId.setExtents(extents);
-            serviceId.setResourceMaintenances(resourceMaintenances);
-            serviceId.setDescriptiveKeywords(descriptiveKeywords);
-            serviceId.setResourceConstraints(resourceConstraints);
-            serviceId.setAssociatedResources(associatedResources);
-            serviceId.setServiceTypeVersions(Set.of("Version 1000+"));
-            // TODO: Coupled resources
-            final DefaultCoupledResource coupledResource = new DefaultCoupledResource();
-            serviceId.getCoupledResources().add(coupledResource);
-            serviceId.setCouplingType(CouplingType.LOOSE);
-            final DefaultOperationMetadata operationMetadata = new DefaultOperationMetadata();
-            {
-                operationMetadata.setOperationName("Authoring");
-                operationMetadata.setOperationDescription(new SimpleInternationalString("Write a book."));
-                operationMetadata.setInvocationName(new SimpleInternationalString("someMethodName"));
-                operationMetadata.getDistributedComputingPlatforms().add(DistributedComputingPlatform.JAVA);
-            }
-            serviceId.getContainsOperations().add(operationMetadata);
-            serviceId.getOperatesOn().add(dataId);
-            md.setIdentificationInfo(List.of(dataId, serviceId));
-        }
-        {
-            // Content info
-            final DefaultCoverageDescription coverageDescription;
-            {
-                coverageDescription = new DefaultCoverageDescription();
-                // Attribute description
-                final DefaultRecordSchema schema = new DefaultRecordSchema(null, null, "IslandFeatures");
-                final Map<CharSequence,Class<?>> members = new LinkedHashMap<>();
-                members.put("city",      String.class);
-                members.put("latitude",  Double.class);
-                members.put("longitude", Double.class);
-                final RecordType recordType = schema.createRecordType("SettledArea", members);
-                coverageDescription.setAttributeDescription(recordType);
-                {
-                    /*
-                     * Attribute group
-                     *   ├─Content type…………………… Auxilliary information
-                     *   ├─Attribute (1 de 2)…… 42
-                     *   │   ├─Description…………… Population density
-                     *   │   └─Name
-                     *   └─Attribute (2 de 2)
-                     *       ├─Description…………… Temperature
-                     *       ├─Max value………………… 22,22
-                     *       ├─Min value………………… 11,11
-                     *       ├─Units…………………………… °C
-                     *       └─Scale factor………… 1,5
-                     */
-                    final DefaultAttributeGroup attributeGroup = new DefaultAttributeGroup();
-                    attributeGroup.getContentTypes().add(CoverageContentType.AUXILLARY_INFORMATION);
-                    // Attributes
-                    final DefaultRangeDimension rangeDimension = new DefaultRangeDimension();
-                    rangeDimension.setDescription(new SimpleInternationalString("Population density"));
-                    rangeDimension.setSequenceIdentifier(Names.createMemberName(null, null, "42", Integer.class));
-                    rangeDimension.getNames().add(new DefaultIdentifier());
-                    final DefaultSampleDimension sampleDimension = new DefaultSampleDimension();
-                    sampleDimension.setDescription(new SimpleInternationalString("Temperature"));
-                    sampleDimension.setMinValue(11.11);
-                    sampleDimension.setMaxValue(22.22);
-                    sampleDimension.setUnits(Units.CELSIUS);
-                    sampleDimension.setScaleFactor(1.5);
-                    attributeGroup.setAttributes(List.of(rangeDimension, sampleDimension));
-                    coverageDescription.getAttributeGroups().add(attributeGroup);
-                }
-            }
-            // Feature Catalogue Description
-            final DefaultFeatureCatalogueDescription featureCatalogueDescription = new DefaultFeatureCatalogueDescription();
-            featureCatalogueDescription.setIncludedWithDataset(true);
-            featureCatalogueDescription.setCompliant(true);
-            md.setContentInfo(List.of(coverageDescription, featureCatalogueDescription));
-        }
-        return md;
-    }
-
-    /**
-     * Tests marshalling of an ISO 19139:2007 document (based on ISO 19115:2003 model).
-     * Current implementation merely tests that marshalling does not produce exception.
-     *
-     * @throws URISyntaxException if an error occurred while creating the metadata object.
-     * @throws JAXBException if an error occurred while marshalling the document.
-     *
-     * @see <a href="https://issues.apache.org/jira/browse/SIS-400">SIS-400</a>
-     */
-    @Test
-    public void testLegacySchema() throws URISyntaxException, JAXBException {
-        legacyXML = true;
-        final DefaultMetadata md = metadata();
-        marshaller.setProperty(XML.METADATA_VERSION, VERSION_2007);
-        marshaller.marshal(md, output);
-        recycle();
-    }
-
-    /**
-     * Tests marshalling of an ISO 19115-3 document (based on ISO 19115:2014 model).
-     * Current implementation merely tests that marshalling does not produce exception.
-     *
-     * @throws URISyntaxException if an error occurred while creating the metadata object.
-     * @throws JAXBException if an error occurred while marshalling the document.
-     *
-     * @see <a href="https://issues.apache.org/jira/browse/SIS-400">SIS-400</a>
-     */
-    @Test
-    public void testCurrentSchema() throws JAXBException, URISyntaxException {
-        final DefaultMetadata md = metadata();
-        marshaller.setProperty(XML.METADATA_VERSION, VERSION_2014);
-        marshaller.marshal(md, output);
-        recycle();
-    }
-
-    /**
-     * Invoked only on success, for recycling the marshaller.
-     */
-    private void recycle() {
-        pool.recycle(marshaller);
-    }
-
-    /**
-     * Invoked when a warning occurred while marshalling a test XML fragment. Expected warnings are
-     * "Cannot find resource for bundle {@code java.util.PropertyResourceBundle}, key <i>Foo</i>".
-     * When marshalling legacy XML only, additional warnings may occur.
-     *
-     * @param  warning  the warning.
-     */
-    @Override
-    public boolean isLoggable(final LogRecord warning) {
-        if (warning.getThrown() instanceof MissingResourceException) {
-            assertNull(warning.getParameters(), "Expected a warning message without parameters.");
-            return false;
-        }
-        final String message = warning.getMessage();
-        if (legacyXML) {
-            assertEquals("IgnoredPropertiesAfterFirst_1", message);
-            assertArrayEquals(new String[] {"RangeDimension"}, warning.getParameters());
-        } else {
-            fail("Unexpected logging message: " + message);
-        }
-        return false;
-    }
-}
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 667c0de..da6501d 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
@@ -97,9 +97,15 @@
                 PresentationForm.DOCUMENT_DIGITAL));
         citation.setAlternateTitles(Set.of(
                 new SimpleInternationalString("Andākarento")));   // Actually a different script of the Japanese title.
-        citation.setCitedResponsibleParties(List.of(
-                new DefaultResponsibility(Role.AUTHOR, null, new DefaultIndividual("Testsuya Toyoda", null, null)),
-                new DefaultResponsibility(Role.EDITOR, Extents.WORLD, new DefaultOrganisation("Kōdansha", null, null, null))));
+
+        final DefaultResponsibleParty author = new DefaultResponsibleParty(Role.AUTHOR);
+        author.setParties(Set.of(new DefaultIndividual("Testsuya Toyoda", null, null)));
+
+        final DefaultResponsibleParty editor = new DefaultResponsibleParty(Role.EDITOR);
+        editor.setParties(Set.of(new DefaultOrganisation("Kōdansha", null, null, null)));
+        editor.setExtents(Set.of(Extents.WORLD));
+
+        citation.setCitedResponsibleParties(List.of(author, editor));
         return citation;
     }
 
@@ -239,7 +245,7 @@
      */
     private void testMarshalling(final Format format) throws JAXBException {
         final var rs = new DefaultOnlineResource(URI.create("https://tools.ietf.org/html/rfc1149"));
-        rs.setName(new SimpleInternationalString("IP over Avian Carriers"));
+        rs.setName("IP over Avian Carriers");
         rs.setDescription(new SimpleInternationalString("High delay, low throughput, and low altitude service."));
         rs.setFunction(OnLineFunction.OFFLINE_ACCESS);
 
@@ -247,10 +253,11 @@
         contact.setContactInstructions(new SimpleInternationalString("Send carrier pigeon."));
         contact.getIdentifierMap().putSpecialized(IdentifierSpace.ID, "ip-protocol");
         final DefaultCitation c = new DefaultCitation("Fight against poverty");
-        c.setCitedResponsibleParties(List.of(
-                new DefaultResponsibility(Role.ORIGINATOR, null, new DefaultIndividual("Maid Marian", null, contact)),
-                new DefaultResponsibility(Role.FUNDER,     null, new DefaultIndividual("Robin Hood",  null, contact))
-        ));
+        final DefaultResponsibleParty r1 = new DefaultResponsibleParty(Role.ORIGINATOR);
+        final DefaultResponsibleParty r2 = new DefaultResponsibleParty(Role.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);
         /*
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 a89e974..198f133 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
@@ -48,8 +48,9 @@
     @Test
     public void testLegacyMarshalling() throws JAXBException {
         final DefaultIndividual  party = new DefaultIndividual("An author", null, null);
-        final DefaultResponsibility  r = new DefaultResponsibility(Role.AUTHOR, null, party);
+        final DefaultResponsibleParty r = new DefaultResponsibleParty(Role.AUTHOR);
         final DefaultCitation citation = new DefaultCitation();
+        r.setParties(Set.of(party));
         citation.setCitedResponsibleParties(Set.of(r));
         final String xml = marshal(citation, VERSION_2007);
         assertXmlEquals("<gmd:CI_Citation xmlns:gco=\"" + LegacyNamespaces.GCO + '"' +
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/citation/HardCodedCitations.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/citation/HardCodedCitations.java
index aeb0ae7..6d3de36 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/citation/HardCodedCitations.java
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/citation/HardCodedCitations.java
@@ -27,6 +27,9 @@
 import org.apache.sis.util.privy.Constants;
 import org.apache.sis.util.privy.URLs;
 
+// Specific to the main and geoapi-3.1 branches:
+import static java.util.Collections.singleton;
+
 
 /**
  * Hard-coded citation constants used for testing purpose only.
@@ -88,8 +91,9 @@
         final DefaultOnlineResource r = new DefaultOnlineResource(URI.create(URLs.EPSG));
         r.setFunction(OnLineFunction.INFORMATION);
 
-        final DefaultResponsibility p = new DefaultResponsibility(Role.PRINCIPAL_INVESTIGATOR, null,
-                new DefaultOrganisation("International Association of Oil & Gas Producers", null, null, new DefaultContact(r)));
+        final DefaultResponsibleParty p = new DefaultResponsibleParty(Role.PRINCIPAL_INVESTIGATOR);
+        p.setParties(singleton(new DefaultOrganisation("International Association of Oil & Gas Producers",
+                null, null, new DefaultContact(r))));
 
         final DefaultCitation c = new DefaultCitation("EPSG Geodetic Parameter Dataset");
         c.getPresentationForms().add(PresentationForm.TABLE_DIGITAL);
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 d6993c4..2191387 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
@@ -26,8 +26,8 @@
 import org.apache.sis.xml.test.TestCase;
 import static org.apache.sis.metadata.Assertions.assertXmlEquals;
 
-// Specific to the geoapi-4.0 branch:
-import org.opengis.metadata.content.PolarisationOrientation;
+// Specific to the main and geoapi-3.1 branches:
+import org.opengis.metadata.content.PolarizationOrientation;
 
 
 /**
@@ -109,7 +109,7 @@
         final DefaultBand band = new DefaultBand();
         band.setNumberOfValues(1000);
         band.setNominalSpatialResolution(10d);
-        band.setDetectedPolarisation(PolarisationOrientation.VERTICAL);
+        band.setDetectedPolarization(PolarizationOrientation.VERTICAL);
         final String actual = marshal(band, version);
         assertXmlEquals(expected, actual, "xmlns:*");
     }
@@ -143,7 +143,7 @@
     private void unmarshal(final String xml, final Integer numberOfValues) throws JAXBException {
         final DefaultBand band = unmarshal(DefaultBand.class, xml);
         assertEquals(Double.valueOf(10), band.getNominalSpatialResolution());
-        assertEquals(PolarisationOrientation.VERTICAL, band.getDetectedPolarisation());
+        assertEquals(PolarizationOrientation.VERTICAL, band.getDetectedPolarization());
         assertEquals(numberOfValues, band.getNumberOfValues());
     }
 }
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/maintenance/DefaultScopeDescriptionTest.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/maintenance/DefaultScopeDescriptionTest.java
index 9d80cb9..625899c 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/maintenance/DefaultScopeDescriptionTest.java
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/maintenance/DefaultScopeDescriptionTest.java
@@ -23,9 +23,6 @@
 import static org.junit.jupiter.api.Assertions.*;
 import org.apache.sis.test.TestCaseWithLogs;
 
-// Specific to the geoapi-4.0 branch:
-import org.apache.sis.util.SimpleInternationalString;
-
 
 /**
  * Tests {@link DefaultScopeDescription}.
@@ -51,7 +48,7 @@
         assertEquals("A dataset", metadata.getDataset());
         loggings.assertNoUnexpectedLog();
 
-        metadata.setOther(new SimpleInternationalString("Other value"));
+        metadata.setOther("Other value");
         assertEquals("Other value", String.valueOf(metadata.getOther()));
         assertNull(metadata.getDataset());
         loggings.assertNextLogContains("dataset", "other");
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 70f25d2..dd4ad99 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
@@ -85,7 +85,6 @@
             source.install();
             verifyFormats(source);
             testSearch(source);
-            testEmptyCollection(source);
             ensureReadOnly(source);
 
             // Opportunistic verification using the database we have at hand.
@@ -142,21 +141,6 @@
     }
 
     /**
-     * Verifies that empty collections are empty, not-null.
-     *
-     * @param  source  the instance to test.
-     * @throws MetadataStoreException if an error occurred while querying the database.
-     */
-    @TestStep
-    public static void testEmptyCollection(final MetadataSource source) throws MetadataStoreException {
-        final Citation c = source.lookup(Citation.class, "SIS");
-        final Collection<?> details = c.getOtherCitationDetails();
-        assertNotNull(details, "Empty collection should not be null.");
-        assertTrue(details.isEmpty(), "Expected an empty collection.");
-        assertSame(Collections.EMPTY_LIST, details, "Collection shall be unmodifiable.");
-    }
-
-    /**
      * Verifies that instances created by {@link MetadataSource} are read-only.
      * In particular, it should not be possible to add elements in the collection.
      *
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 6e0456e..b92e56f 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
@@ -122,7 +122,7 @@
         assertEquals("EPSG",      source.search(HardCodedCitations.EPSG));
         assertEquals("SIS",       source.search(HardCodedCitations.SIS));
         assertNull  (             source.search(HardCodedCitations.ISO_19111));
-        assertEquals("EPSG",      source.search(TestUtilities.getSingleton(
+        assertEquals("{rp}EPSG",  source.search(TestUtilities.getSingleton(
                 HardCodedCitations.EPSG.getCitedResponsibleParties())));
     }
 
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 fba91a2..9721162 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
@@ -28,8 +28,8 @@
 import org.apache.sis.util.privy.CollectionsExt;
 import org.apache.sis.xml.bind.gco.GO_GenericName;
 
-// Specific to the geoapi-4.0 branch:
-import org.opengis.metadata.Identifier;
+// Specific to the main and geoapi-3.1 branches:
+import org.opengis.referencing.ReferenceIdentifier;
 
 
 /**
@@ -41,7 +41,7 @@
  */
 @SuppressWarnings("serial")
 @XmlRootElement(name = "IO_IdentifiedObject")
-public class IdentifiedObjectMock implements IdentifiedObject, Identifier, Serializable {
+public class IdentifiedObjectMock implements IdentifiedObject, ReferenceIdentifier, Serializable {
     /**
      * The object name to be returned by {@link #getCode()}.
      */
@@ -99,7 +99,7 @@
      * @return the name of this object, or {@code null} if none.
      */
     @Override
-    public final Identifier getName() {
+    public final ReferenceIdentifier getName() {
         return (code != null) ? this : null;
     }
 
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 4b93157..2ad61c5 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
@@ -25,6 +25,10 @@
 import org.opengis.referencing.datum.VerticalDatum;
 import org.apache.sis.measure.Units;
 
+// Specific to the main and geoapi-3.1 branches:
+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;
@@ -119,6 +123,8 @@
     }
 
     @Override public String                      getAbbreviation()      {return up ? "h" : "d";}
+    @Override public InternationalString         getScope()             {return null;}
+    @Override public Extent                      getDomainOfValidity()  {return null;}
     @Override public Optional<RealizationMethod> getRealizationMethod() {return Optional.ofNullable(method);}
     @Override public VerticalDatum               getDatum()             {return this;}
     @Override public VerticalCS                  getCoordinateSystem()  {return this;}
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/bind/cat/EnumMarshallingTest.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/bind/cat/EnumMarshallingTest.java
index 456988d..965ee8f 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/bind/cat/EnumMarshallingTest.java
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/bind/cat/EnumMarshallingTest.java
@@ -30,9 +30,6 @@
 import static org.apache.sis.test.Assertions.assertSetEquals;
 import static org.apache.sis.metadata.Assertions.assertXmlEquals;
 
-// Specific to the geoapi-4.0 branch:
-import java.util.EnumSet;
-
 
 /**
  * Tests the XML marshalling of {@code Enum}.
@@ -80,7 +77,6 @@
          * Unmarshal the above XML and verify that we find all the topic categories.
          */
         final Collection<TopicCategory> unmarshalled = unmarshal(DefaultDataIdentification.class, expected).getTopicCategories();
-        assertInstanceOf(EnumSet.class, unmarshalled);
         assertSetEquals(topics, unmarshalled);
     }
 }
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 838440a..a80c7c6 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
@@ -485,7 +485,7 @@
          * We check only the namespace start, since some specifications define many namespaces
          * under a common root (e.g. "http://standards.iso.org/iso/19115/-3/").
          */
-        if (uml != null) {
+        if (uml != null && false) {     // This verification is available only on development branches.
             final String expected = getExpectedNamespaceStart(impl, uml);
             if (!namespace.startsWith(expected)) {
                 fail("Expected " + expected + "… namespace for that ISO specification but got " + namespace);
@@ -553,6 +553,18 @@
     protected boolean isIgnored(final Method method) {
         switch (method.getName()) {
             /*
+             * Spelling changed.
+             */
+            case "getCenterPoint": {
+                return true;
+            }
+            /*
+             * Method that override an annotated method in parent class.
+             */
+            case "getUnits": {
+                return org.opengis.metadata.content.Band.class.isAssignableFrom(method.getDeclaringClass());
+            }
+            /*
              * Types for which JAXB binding has not yet implemented.
              */
             case "getGeographicCoordinates": {
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
index 3146b3a..8650b2a 100644
--- 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
@@ -407,6 +407,9 @@
                     } 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());
                         }
@@ -514,20 +517,4 @@
         }
         return builder.append(':').append(System.lineSeparator());
     }
-
-    /**
-     * Verifies if there is any unused namespace or adapter in package-info file.
-     */
-    final void reportUnused() throws SchemaException {
-        for (final Map.Entry<String,Boolean> entry : namespaceIsUsed.entrySet()) {
-            if (!entry.getValue()) {
-                throw new SchemaException(String.format("Unused namespace in package %s:%n%s", packageName, entry.getKey()));
-            }
-        }
-        for (final Map.Entry<Class<?>,Boolean> entry : adapterIsUsed.entrySet()) {
-            if (!entry.getValue()) {
-                throw new SchemaException(String.format("Unused adapter in package %s for %s.", packageName, entry.getKey()));
-            }
-        }
-    }
 }
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
index 4847f6a..af1dcc8 100644
--- 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
@@ -142,9 +142,6 @@
         } catch (DirectoryIteratorException e) {
             throw e.getCause();
         }
-        if (verifier != null) {
-            verifier.reportUnused();
-        }
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.openoffice/bundle/README.md b/endorsed/src/org.apache.sis.openoffice/bundle/README.md
index acc06e2..e21a698 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-2.0-SNAPSHOT.oxt --log-file log.txt
+unopkg add apache-sis-1.x-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 2fbfc7b..3883c2d 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
@@ -45,10 +45,6 @@
 // Specific to the geoapi-3.1 and geoapi-4.0 branches:
 import org.opengis.referencing.ObjectDomain;
 
-// Specific to the geoapi-4.0 branch:
-import org.opengis.referencing.crs.CRSAuthorityFactory;
-import org.apache.sis.referencing.factory.GeodeticAuthorityFactory;
-
 
 /**
  * Implements the {@link XReferencing} methods to make available to Apache OpenOffice.
@@ -110,6 +106,7 @@
      * @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
     {
@@ -124,12 +121,7 @@
                         type = CodeType.guess(codeOrPath);
                     }
                     if (type.equals(CodeType.URN)) {
-                        CRSAuthorityFactory factory = CRS.getAuthorityFactory(null);
-                        if (factory instanceof GeodeticAuthorityFactory) {
-                            object = ((GeodeticAuthorityFactory) factory).createObject(codeOrPath);
-                        } else {
-                            object = factory.createCoordinateReferenceSystem(codeOrPath);
-                        }
+                        object = CRS.getAuthorityFactory(null).createObject(codeOrPath);
                     } else if (type.isCRS) {
                         object = CRS.forCode(codeOrPath);
                     } else {
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 be624a5..4511f97 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
@@ -56,13 +56,13 @@
 import org.apache.sis.coverage.grid.GridGeometry;
 import org.apache.sis.coverage.grid.GridExtent;
 
+// 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 geoapi-4.0 branch:
-import org.opengis.coordinate.MismatchedCoordinateMetadataException;
-
 
 /**
  * Common abstraction for implementations that manage the display and user manipulation
@@ -739,7 +739,7 @@
                 return;
             }
         }
-        throw new MismatchedDimensionException(errors().getString(
+        throw new org.opengis.geometry.MismatchedDimensionException(errors().getString(
                 Errors.Keys.MismatchedDimension_3, OBJECTIVE_TO_DISPLAY_PROPERTY, expected, actual));
     }
 
@@ -798,7 +798,7 @@
         ArgumentChecks.ensureNonNull(DISPLAY_BOUNDS_PROPERTY, newValue);
         final CoordinateReferenceSystem crs = newValue.getCoordinateReferenceSystem();
         if (crs != null && !Utilities.equalsIgnoreMetadata(getDisplayCRS(), crs)) {
-            throw new MismatchedCoordinateMetadataException(errors().getString(
+            throw new MismatchedReferenceSystemException(errors().getString(
                     Errors.Keys.IllegalCoordinateSystem_1, IdentifiedObjects.getDisplayName(crs, getLocale())));
         }
         final GeneralEnvelope oldValue = new GeneralEnvelope(displayBounds);
diff --git a/endorsed/src/org.apache.sis.profile.france/main/org/apache/sis/xml/bind/fra/DirectReferenceSystem.java b/endorsed/src/org.apache.sis.profile.france/main/org/apache/sis/xml/bind/fra/DirectReferenceSystem.java
index 58e298a..cca06a1 100644
--- a/endorsed/src/org.apache.sis.profile.france/main/org/apache/sis/xml/bind/fra/DirectReferenceSystem.java
+++ b/endorsed/src/org.apache.sis.profile.france/main/org/apache/sis/xml/bind/fra/DirectReferenceSystem.java
@@ -22,8 +22,8 @@
 import org.apache.sis.xml.bind.metadata.replace.ReferenceSystemMetadata;
 import org.apache.sis.util.ComparisonMode;
 
-// Specific to the geoapi-4.0 branch:
-import org.opengis.metadata.Identifier;
+// Specific to the main and geoapi-3.1 branches:
+import org.opengis.referencing.ReferenceIdentifier;
 
 
 /**
@@ -70,7 +70,7 @@
      *
      * @param  identifier  the reference system identifier.
      */
-    public DirectReferenceSystem(final Identifier identifier) {
+    public DirectReferenceSystem(final ReferenceIdentifier identifier) {
         super(identifier);
     }
 
diff --git a/endorsed/src/org.apache.sis.profile.france/main/org/apache/sis/xml/bind/fra/IndirectReferenceSystem.java b/endorsed/src/org.apache.sis.profile.france/main/org/apache/sis/xml/bind/fra/IndirectReferenceSystem.java
index 9d85fd5..791b5e6 100644
--- a/endorsed/src/org.apache.sis.profile.france/main/org/apache/sis/xml/bind/fra/IndirectReferenceSystem.java
+++ b/endorsed/src/org.apache.sis.profile.france/main/org/apache/sis/xml/bind/fra/IndirectReferenceSystem.java
@@ -22,8 +22,8 @@
 import org.apache.sis.xml.bind.metadata.replace.ReferenceSystemMetadata;
 import org.apache.sis.util.ComparisonMode;
 
-// Specific to the geoapi-4.0 branch:
-import org.opengis.metadata.Identifier;
+// Specific to the main and geoapi-3.1 branches:
+import org.opengis.referencing.ReferenceIdentifier;
 
 
 /**
@@ -70,7 +70,7 @@
      *
      * @param  identifier  the reference system identifier.
      */
-    public IndirectReferenceSystem(final Identifier identifier) {
+    public IndirectReferenceSystem(final ReferenceIdentifier identifier) {
         super(identifier);
     }
 
diff --git a/endorsed/src/org.apache.sis.profile.france/test/org/apache/sis/profile/france/FrenchProfileTest.java b/endorsed/src/org.apache.sis.profile.france/test/org/apache/sis/profile/france/FrenchProfileTest.java
index f7670e4..fd3fa0c 100644
--- a/endorsed/src/org.apache.sis.profile.france/test/org/apache/sis/profile/france/FrenchProfileTest.java
+++ b/endorsed/src/org.apache.sis.profile.france/test/org/apache/sis/profile/france/FrenchProfileTest.java
@@ -33,8 +33,8 @@
 import org.apache.sis.test.TestCase;
 import static org.apache.sis.test.TestUtilities.getSingleton;
 
-// Specific to the geoapi-4.0 branch:
-import org.apache.sis.metadata.iso.DefaultIdentifier;
+// Specific to the main and geoapi-3.1 branches:
+import org.apache.sis.xml.bind.metadata.replace.RS_Identifier;
 
 
 /**
@@ -83,7 +83,7 @@
     public void testReferenceSystemToAFNOR() {
         ReferenceSystem std, fra;
 
-        std = new ReferenceSystemMetadata(new DefaultIdentifier("EPSG", "4326", null));
+        std = new ReferenceSystemMetadata(new RS_Identifier("EPSG", "4326", null));
         fra = FrenchProfile.toAFNOR(std, false);
         assertInstanceOf(DirectReferenceSystem.class, fra);
         assertSame(fra, FrenchProfile.toAFNOR(fra));
diff --git a/endorsed/src/org.apache.sis.profile.france/test/org/apache/sis/xml/bind/fra/DirectReferenceSystemTest.java b/endorsed/src/org.apache.sis.profile.france/test/org/apache/sis/xml/bind/fra/DirectReferenceSystemTest.java
index f3bda0d..ad06b36 100644
--- a/endorsed/src/org.apache.sis.profile.france/test/org/apache/sis/xml/bind/fra/DirectReferenceSystemTest.java
+++ b/endorsed/src/org.apache.sis.profile.france/test/org/apache/sis/xml/bind/fra/DirectReferenceSystemTest.java
@@ -32,9 +32,9 @@
 import org.apache.sis.test.TestUtilities;
 import org.apache.sis.metadata.iso.citation.HardCodedCitations;
 
-// Specific to the geoapi-4.0 branch:
-import org.opengis.metadata.citation.Responsibility;
-import org.apache.sis.metadata.iso.DefaultIdentifier;
+// Specific to the main and geoapi-3.1 branches:
+import org.opengis.metadata.citation.ResponsibleParty;
+import org.apache.sis.xml.bind.metadata.replace.RS_Identifier;
 
 
 /**
@@ -69,12 +69,12 @@
     private static DefaultMetadata createMetadata(final boolean legacy) {
         final DefaultMetadata metadata = new DefaultMetadata();
         final DefaultCitation citation = new DefaultCitation("EPSG Geodetic Parameter Dataset");
-        Collection<Responsibility> r = HardCodedCitations.EPSG.getCitedResponsibleParties();
+        Collection<ResponsibleParty> r = HardCodedCitations.EPSG.getCitedResponsibleParties();
         if (legacy) {
             r = Set.of(new DefaultResponsibleParty(TestUtilities.getSingleton(r)));
         }
         citation.setCitedResponsibleParties(r);
-        final DirectReferenceSystem refSys = new DirectReferenceSystem(new DefaultIdentifier(citation, "4326"));
+        final DirectReferenceSystem refSys = new DirectReferenceSystem(new RS_Identifier(citation, "4326"));
         metadata.setReferenceSystemInfo(Set.of(refSys));
         return metadata;
     }
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 cd30ab4..3e7b7c4 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
@@ -128,7 +128,7 @@
             if (crs != null) {
                 final CoordinateReferenceSystem other = position.getCoordinateReferenceSystem();
                 if (other != null && !Utilities.equalsIgnoreMetadata(crs, other)) {
-                    throw new MismatchedCoordinateMetadataException(Errors.format(Errors.Keys.MismatchedCRS));
+                    throw new MismatchedReferenceSystemException(Errors.format(Errors.Keys.MismatchedCRS));
                 }
             }
             for (int i=0; i<dimension; i++) {
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 4bd1071..89f1a85 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
@@ -197,7 +197,7 @@
             return crs2;
         } else {
             if (crs2 != null && !crs1.equals(crs2)) {
-                throw new MismatchedCoordinateMetadataException(Errors.format(Errors.Keys.MismatchedCRS));
+                throw new MismatchedReferenceSystemException(Errors.format(Errors.Keys.MismatchedCRS));
             }
             return crs1;
         }
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 22b675f..c7b46a7 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
@@ -312,7 +312,7 @@
      */
     static void ensureSameDimension(final int dim1, final int dim2) throws MismatchedDimensionException {
         if (dim1 != dim2) {
-            throw new MismatchedDimensionException(Errors.format(
+            throw new org.opengis.geometry.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 efc89b4..8b353f2 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
@@ -580,7 +580,7 @@
                     }
                     types  [i] = DATE;
                     formats[i] = getFormat(Date.class);
-                    epochs [i] = TemporalDate.toInstant(((TemporalCRS) t).getDatum().getOrigin(), null);
+                    epochs [i] = TemporalDate.toInstant(((TemporalCRS) t).getDatum().getOrigin());
                     setConverter(dimension, i, unit.asType(Time.class).getConverterTo(Units.SECOND));
                     if (direction == AxisDirection.PAST) {
                         negate(i);
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 8ab34a9..9d4d853 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
@@ -31,7 +31,7 @@
 import java.time.Instant;
 import org.opengis.geometry.Envelope;
 import org.opengis.geometry.DirectPosition;
-import org.opengis.coordinate.MismatchedDimensionException;
+import org.opengis.geometry.MismatchedDimensionException;
 import org.opengis.referencing.cs.CoordinateSystem;
 import org.opengis.referencing.cs.CoordinateSystemAxis;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
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 72c8a46..0d3086f 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
@@ -386,7 +386,7 @@
         }
         final int d = corners.length >>> 1;
         if (d != dimension) {
-            throw new MismatchedDimensionException(Errors.format(
+            throw new org.opengis.geometry.MismatchedDimensionException(Errors.format(
                     Errors.Keys.MismatchedDimension_3, "coordinates", dimension, d));
         }
     }
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
new file mode 100644
index 0000000..5bfd193
--- /dev/null
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/MismatchedReferenceSystemException.java
@@ -0,0 +1,67 @@
+/*
+ * 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;
+
+// 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
+ * reference systems of components.
+ *
+ * @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 {
+    /**
+     * Serial number for inter-operability with different versions.
+     */
+    private static final long serialVersionUID = 6222334569692693273L;
+
+    /**
+     * Creates an exception with no message.
+     */
+    public MismatchedReferenceSystemException() {
+        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 MismatchedReferenceSystemException(final String message) {
+        super(message);
+    }
+
+    /**
+     * Creates an exception with the specified message and cause.
+     *
+     * @param  message The detail message. The detail message is saved for
+     *         later retrieval by the {@link #getMessage()} method.
+     * @param  cause The cause.
+     */
+    public MismatchedReferenceSystemException(final String message, final Throwable cause) {
+        super(message, cause);
+    }
+}
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 c91711c..b4205a4 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
@@ -872,7 +872,7 @@
             }
         }
         if (showRemarks) {
-            appendOnNewLine(WKTKeywords.Remark, object.getRemarks().orElse(null), ElementKind.REMARKS);
+            appendOnNewLine(WKTKeywords.Remark, object.getRemarks(), ElementKind.REMARKS);
         }
         isComplement = false;
     }
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 b101d95..dc6a74f 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
@@ -84,13 +84,12 @@
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.iso.Types;
 
+// 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 geoapi-4.0 branch:
-import org.apache.sis.referencing.crs.DefaultImageCRS;
-import org.apache.sis.referencing.datum.DefaultImageDatum;
-
 
 /**
  * Well Known Text (WKT) parser for referencing objects. This include, but is not limited too,
@@ -454,11 +453,11 @@
             final var id = new ImmutableIdentifier(Citations.fromName(authority),
                     codeSpace, code, (version != null) ? version.toString() : null, null);
             properties.merge(IdentifiedObject.IDENTIFIERS_KEY, id, (previous, toAdd) -> {
-                final var more = (Identifier) toAdd;
-                if (previous instanceof Identifier) {
-                    return new Identifier[] {(Identifier) previous, more};
+                final var more = (ReferenceIdentifier) toAdd;
+                if (previous instanceof ReferenceIdentifier) {
+                    return new ReferenceIdentifier[] {(ReferenceIdentifier) previous, more};
                 } else {
-                    return ArraysExt.append((Identifier[]) previous, more);
+                    return ArraysExt.append((ReferenceIdentifier[]) previous, more);
                 }
             });
             // REMINDER: values associated to IDENTIFIERS_KEY shall be recognized by `toIdentifier(Object)`.
@@ -1447,7 +1446,6 @@
             return null;
         }
         final String name = element.pullString("name");
-        @SuppressWarnings("deprecation")
         RealizationMethod method = null;
         if (isWKT1) {
             method = VerticalDatumTypes.fromLegacy(element.pullInteger("datum"));
@@ -1566,14 +1564,20 @@
      * @throws ParseException if the {@code "ImageDatum"} element cannot be parsed.
      */
     @SuppressWarnings("removal")
-    private DefaultImageDatum parseImageDatum(final int mode, final Element parent) throws ParseException {
+    private ImageDatum parseImageDatum(final int mode, final Element parent) throws ParseException {
         final Element element = parent.pullElement(mode, WKTKeywords.ImageDatum, WKTKeywords.IDatum);
         if (element == null) {
             return null;
         }
         final String name = element.pullString("name");
-        final String pixelInCell = element.pullVoidElement("pixelInCell").keyword;
-        return new DefaultImageDatum(parseAnchorAndClose(element, name), pixelInCell);
+        final PixelInCell pixelInCell = Types.forCodeName(PixelInCell.class,
+                element.pullVoidElement("pixelInCell").keyword, PixelInCell::valueOf);
+        final DatumFactory datumFactory = factories.getDatumFactory();
+        try {
+            return datumFactory.createImageDatum(parseAnchorAndClose(element, name), pixelInCell);
+        } catch (FactoryException exception) {
+            throw element.parseFailed(exception);
+        }
     }
 
     /**
@@ -1667,7 +1671,7 @@
      * @throws ParseException if the {@code "ImageCRS"} element cannot be parsed.
      */
     @SuppressWarnings("removal")
-    private DefaultImageCRS parseImageCRS(final int mode, final Element parent) throws ParseException {
+    private ImageCRS parseImageCRS(final int mode, final Element parent) throws ParseException {
         final Element element = parent.pullElement(mode, WKTKeywords.ImageCRS);
         if (element == null) {
             return null;
@@ -1680,7 +1684,7 @@
             cs = parseCoordinateSystem(element, WKTKeywords.Cartesian, 2, false, unit, datum);
             final Map<String,?> properties = parseMetadataAndClose(element, name, datum);
             if (cs instanceof AffineCS) {
-                return new DefaultImageCRS(properties, datum, (AffineCS) cs);
+                return factories.getCRSFactory().createImageCRS(properties, datum, (AffineCS) cs);
             }
         } catch (FactoryException exception) {
             throw element.parseFailed(exception);
@@ -1709,7 +1713,7 @@
      * @param  dimension  the minimal number of dimensions (usually 2).
      * @param  csType     the default coordinate system type, or {@code null} if unknown.
      *                    Should be non-null only when parsing a {@link DerivedCRS#getBaseCRS()} component.
-     * @return the {@code "GeodeticCRS"} element as a {@link GeographicCRS} or {@link GeodeticCRS} object.
+     * @return the {@code "GeodeticCRS"} element as a {@link GeographicCRS} or {@link GeocentricCRS} object.
      * @throws ParseException if the {@code "GeodeticCRS"} element cannot be parsed.
      *
      * @see org.apache.sis.referencing.crs.DefaultGeographicCRS#formatTo(Formatter)
@@ -1880,7 +1884,6 @@
      * @return the {@code "VerticalCRS"} element as a {@link VerticalCRS} object.
      * @throws ParseException if the {@code "VerticalCRS"} element cannot be parsed.
      */
-    @SuppressWarnings("deprecation")
     private SingleCRS parseVerticalCRS(final int mode, final Element parent, final boolean isBaseCRS)
             throws ParseException
     {
@@ -2113,7 +2116,7 @@
         final boolean   isWKT1 = element.getKeywordIndex() == 2;                // Index of "ProjCS" above.
         final String    name   = element.pullString("name");
         final SingleCRS geoCRS = parseGeodeticCRS(MANDATORY, element, 2, WKTKeywords.ellipsoidal);
-        if (!(geoCRS instanceof GeodeticCRS)) {
+        if (!(geoCRS instanceof GeographicCRS)) {
             throw new UnparsableObjectException(errorLocale, Errors.Keys.IllegalCRSType_1,
                     new Object[] {geoCRS.getClass()}, element.offset);
         }
@@ -2156,7 +2159,7 @@
             final Map<String,?> properties = parseMetadataAndClose(element, name, conversion);
             if (cs instanceof CartesianCS) {
                 final CRSFactory crsFactory = factories.getCRSFactory();
-                return crsFactory.createProjectedCRS(properties, (GeodeticCRS) geoCRS, conversion, (CartesianCS) cs);
+                return crsFactory.createProjectedCRS(properties, (GeographicCRS) geoCRS, conversion, (CartesianCS) cs);
             }
         } catch (FactoryException exception) {
             throw element.parseFailed(exception);
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 4192edc..eeafaee 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
@@ -59,6 +59,9 @@
 import org.apache.sis.metadata.privy.NameToIdentifier;
 import org.apache.sis.pending.jdk.JDK19;
 
+// 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;
 
@@ -839,7 +842,7 @@
              * Put the first identifier in the first column. If no identifier has a codespace in the list
              * supplied by the user, then we will use the first identifier (any codespace) as a fallback.
              */
-            final Set<Identifier> identifiers = object.getIdentifiers();
+            final Set<ReferenceIdentifier> identifiers = object.getIdentifiers();
             if (identifiers != null) {                                              // Paranoiac check.
                 Identifier identifier = null;
                 for (final Identifier candidate : identifiers) {
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 24943bf..a98e17d 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
@@ -45,6 +45,9 @@
 import static org.apache.sis.util.CharSequences.spaces;
 import static org.apache.sis.util.privy.Constants.DEFAULT_SEPARATOR;
 
+// Specific to the main and geoapi-3.1 branches:
+import org.opengis.util.InternationalString;
+
 
 /**
  * A row in the table to be formatted by {@link ParameterFormat}.
@@ -182,11 +185,12 @@
         /*
          * Take the remarks, if any.
          */
-        object.getRemarks().ifPresent((r) -> {
+        final InternationalString r = object.getRemarks();
+        if (r != null) {
             final int n = remarks.size() + 1;
             final Integer p = remarks.putIfAbsent(r.toString(locale), n);
             this.remarks = (p != null) ? p : n;
-        });
+        }
     }
 
     /**
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 679b1ed..8b4b5d2 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
@@ -74,6 +74,9 @@
 import static org.apache.sis.util.privy.CollectionsExt.nonEmpty;
 import static org.apache.sis.util.privy.CollectionsExt.immutableSet;
 
+// 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;
 
@@ -119,7 +122,7 @@
  * </ul>
  *
  * <h2>Immutability and thread safety</h2>
- * This base class is immutable if the {@link Citation}, {@link Identifier}, {@link GenericName} and
+ * This base class is immutable if the {@link Citation}, {@link ReferenceIdentifier}, {@link GenericName} and
  * {@link InternationalString} instances given to the constructor are also immutable. Most SIS subclasses and
  * related classes are immutable under similar conditions. This means that unless otherwise noted in the javadoc,
  * {@code IdentifiedObject} instances created using only SIS factories and static constants can be shared by many
@@ -182,13 +185,13 @@
      * The name for this object or code. Shall never be {@code null}.
      *
      * <p><b>Consider this field as final!</b>
-     * This field is modified only at unmarshalling time by {@link Names#add(Identifier)}.</p>
+     * This field is modified only at unmarshalling time by {@code Names.add(Identifier)}.</p>
      *
      * @see #getName()
      * @see #getNames()
      */
     @SuppressWarnings("serial")         // Most SIS implementations are serializable.
-    private Identifier name;
+    private ReferenceIdentifier name;
 
     /**
      * An alternative name by which this object is identified, or {@code null} if none.
@@ -196,7 +199,7 @@
      * we may get both on unmarshalling.
      *
      * <p><b>Consider this field as final!</b>
-     * This field is modified only at unmarshalling time by {@link Names#add(Identifier)}.</p>
+     * This field is modified only at unmarshalling time by {@code Names.add(Identifier)}.</p>
      */
     @SuppressWarnings("serial")         // Most SIS implementations are serializable.
     private Collection<GenericName> alias;
@@ -212,7 +215,7 @@
      * @see #getIdentifier()
      */
     @SuppressWarnings("serial")         // Most SIS implementations are serializable.
-    private Set<Identifier> identifiers;
+    private Set<ReferenceIdentifier> identifiers;
 
     /**
      * Scope and area for which this object is valid, or {@code null} if none.
@@ -269,7 +272,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.metadata.Identifier#AUTHORITY_KEY}</td>
@@ -280,11 +283,11 @@
      *     <td>{@link String}</td>
      *     <td>{@link NamedIdentifier#getCode()} on the {@linkplain #getName() name}</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 NamedIdentifier#getCodeSpace()} on the {@linkplain #getName() name}</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 NamedIdentifier#getVersion()} on the {@linkplain #getName() name}</td>
      *   </tr><tr>
@@ -297,7 +300,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#DOMAINS_KEY}</td>
@@ -364,8 +367,8 @@
                         .getString(Errors.Keys.MissingValueForProperty_1, NAME_KEY));
             }
             name = new NamedIdentifier(PropertiesConverter.convert(properties));
-        } else if (value instanceof Identifier) {
-            name = (Identifier) value;
+        } else if (value instanceof ReferenceIdentifier) {
+            name = (ReferenceIdentifier) value;
         } else {
             throw illegalPropertyType(properties, NAME_KEY, value);
         }
@@ -387,10 +390,10 @@
         // "identifiers": Identifier or Identifier[]
         // -----------------------------------------
         value = properties.get(IDENTIFIERS_KEY);
-        if (value instanceof Identifier) {
-            identifiers = Collections.singleton((Identifier) value);
-        } else if (value instanceof Identifier[]) {
-            identifiers = immutableSet(true, (Identifier[]) value);
+        if (value instanceof ReferenceIdentifier) {
+            identifiers = Collections.singleton((ReferenceIdentifier) value);
+        } else if (value instanceof ReferenceIdentifier[]) {
+            identifiers = immutableSet(true, (ReferenceIdentifier[]) value);
         } else if (value != null) {
             throw illegalPropertyType(properties, IDENTIFIERS_KEY, value);
         }
@@ -456,7 +459,7 @@
         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());
-        remarks     =          object.getRemarks().orElse(null);
+        remarks     =          object.getRemarks();
         deprecated  = (object instanceof Deprecable) ? ((Deprecable) object).isDeprecated() : false;
     }
 
@@ -527,7 +530,7 @@
      * @see IdentifiedObjects#getName(IdentifiedObject, Citation)
      */
     @Override
-    public Identifier getName() {
+    public ReferenceIdentifier getName() {
         return name;
     }
 
@@ -552,7 +555,7 @@
      * @see IdentifiedObjects#getIdentifier(IdentifiedObject, Citation)
      */
     @Override
-    public Set<Identifier> getIdentifiers() {
+    public Set<ReferenceIdentifier> getIdentifiers() {
         return nonNull(identifiers);    // Needs to be null-safe because we may have a null value on unmarshalling.
     }
 
@@ -592,11 +595,11 @@
      * If this object {@linkplain #isDeprecated() is deprecated}, then the remarks should give
      * indication about the replacement (e.g. <q>superceded by …</q>).
      *
-     * @return the remarks.
+     * @return the remarks, or {@code null} if none.
      */
     @Override
-    public Optional<InternationalString> getRemarks() {
-        return Optional.ofNullable(remarks);
+    public InternationalString getRemarks() {
+        return remarks;
     }
 
     /**
@@ -1061,7 +1064,7 @@
         if (identifiers != null) {
             propertyAlreadySet("setIdentifier", "identifier");
         } else if (identifier != null) {
-            final Identifier id = identifier.getIdentifier();
+            final ReferenceIdentifier id = identifier.getIdentifier();
             if (id != null) {
                 identifiers = Collections.singleton(id);
                 ScopedIdentifier<IdentifiedObject> key = new ScopedIdentifier<>(getInterface(), identifier.toString());
@@ -1091,7 +1094,7 @@
      * @see <a href="https://java.net/jira/browse/JAXB-488">JAXB-488</a>
      */
     @XmlElement(name = "name", required = true)
-    final Collection<Identifier> getNames() {
+    final Collection<ReferenceIdentifier> getNames() {
         return new Names();
     }
 
@@ -1108,7 +1111,7 @@
      * subclasses, this is too late. For example, {@code DefaultOperationMethod} may need to know the operation name
      * before to parse the parameters.
      */
-    private final class Names extends AbstractCollection<Identifier> {
+    private final class Names extends AbstractCollection<ReferenceIdentifier> {
         /**
          * Invoked by JAXB before to write in the collection at unmarshalling time.
          * Do nothing since our object is already empty.
@@ -1129,7 +1132,7 @@
          * Returns an iterator over the name and aliases that are instance of {@link Identifier}.
          */
         @Override
-        public Iterator<Identifier> iterator() {
+        public Iterator<ReferenceIdentifier> iterator() {
             return new NameIterator(AbstractIdentifiedObject.this);
         }
 
@@ -1143,7 +1146,7 @@
          * See <a href="https://java.net/jira/browse/JAXB-488">JAXB-488</a> for more information.</p>
          */
         @Override
-        public boolean add(final Identifier id) {
+        public boolean add(final ReferenceIdentifier id) {
             if (NameIterator.isUnnamed(name)) {
                 name = id;
             } else {
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 943c717..c9433e3 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
@@ -41,6 +41,9 @@
 import org.apache.sis.util.iso.Types;
 import org.apache.sis.util.resources.Errors;
 
+// Specific to the main and geoapi-3.1 branches:
+import org.opengis.referencing.ReferenceIdentifier;
+
 
 /**
  * Base class of builders for various kinds of {@link IdentifiedObject}. This class provides convenience methods
@@ -93,7 +96,7 @@
  *       The result is a {@linkplain org.apache.sis.util.iso.DefaultScopedName scoped name} or identifier,
  *       in which the code space information is shown by the {@code toString()} method.</li>
  *
- *   <li>The {@link #addIdentifier(Identifier)}, {@link #addName(Identifier)} and {@link #addName(GenericName)}
+ *   <li>The {@link #addIdentifier(ReferenceIdentifier)}, {@link #addName(ReferenceIdentifier)} and {@link #addName(GenericName)}
  *       methods take the given object <em>as-is</em>. Any authority, code space, version or description
  *       information given to the {@code Builder} are ignored.</li>
  * </ul>
@@ -200,7 +203,7 @@
     /**
      * A temporary list for identifiers, before to assign them to the {@link #properties}.
      */
-    private final List<Identifier> identifiers;
+    private final List<ReferenceIdentifier> identifiers;
 
     /**
      * The codespace as a {@code NameSpace} object, or {@code null} if not yet created.
@@ -274,7 +277,7 @@
         if (object != null) {
             properties.putAll(IdentifiedObjects.getProperties(object));
             final GenericName[] valueAlias = (GenericName[]) properties.remove(IdentifiedObject.ALIAS_KEY);
-            final Identifier[]  valueIds   = (Identifier[])  properties.remove(IdentifiedObject.IDENTIFIERS_KEY);
+            final ReferenceIdentifier[] valueIds = (ReferenceIdentifier[]) properties.remove(IdentifiedObject.IDENTIFIERS_KEY);
             if (valueAlias != null) Collections.addAll(aliases, valueAlias);
             if (valueIds   != null) Collections.addAll(identifiers, valueIds);
         }
@@ -323,7 +326,7 @@
      * then the new identifier will also contain the user supplied code space and version (if any).
      * The new identifier will be marked as deprecated if {@link #isDeprecated()} returns {@code true}.
      */
-    private Identifier createIdentifier(final Citation authority, final String identifier) {
+    private ReferenceIdentifier createIdentifier(final Citation authority, final String identifier) {
         final String codeSpace;
         final String version;
         if (authority == getAuthority()) {
@@ -341,7 +344,9 @@
      * Creates an identifier for the given authority, code space and version.
      * The new identifier will be marked as deprecated if {@link #isDeprecated()} returns {@code true}.
      */
-    private Identifier createIdentifier(final Citation authority, final String codeSpace, final String identifier, final String version) {
+    private ReferenceIdentifier createIdentifier(final Citation authority,
+            final String codeSpace, final String identifier, final String version)
+    {
         if (isDeprecated()) {
             return new DeprecatedCode(authority, codeSpace, identifier, version, null, getRemarks());
         } else {
@@ -353,8 +358,8 @@
      * Converts the given name into an identifier. Note that {@link NamedIdentifier}
      * implements both {@link GenericName} and {@link Identifier} interfaces.
      */
-    private static Identifier toIdentifier(final GenericName name) {
-        return (name instanceof Identifier) ? (Identifier) name : new NamedIdentifier(name);
+    private static ReferenceIdentifier toIdentifier(final GenericName name) {
+        return (name instanceof ReferenceIdentifier) ? (ReferenceIdentifier) name : new NamedIdentifier(name);
     }
 
     /**
@@ -579,7 +584,7 @@
      * @param  name  the {@code IdentifiedObject} name as an identifier.
      * @return {@code this}, for method call chaining.
      */
-    public B addName(final Identifier name) {
+    public B addName(final ReferenceIdentifier name) {
         if (properties.putIfAbsent(IdentifiedObject.NAME_KEY, Objects.requireNonNull(name)) != null) {
             // A primary name is already present. Add the given name as an alias instead.
             aliases.add(name instanceof GenericName ? (GenericName) name : new NamedIdentifier(name));
@@ -670,7 +675,7 @@
      * @param  identifier  the {@code IdentifiedObject} identifier.
      * @return {@code this}, for method call chaining.
      */
-    public B addIdentifier(final Identifier identifier) {
+    public B addIdentifier(final ReferenceIdentifier identifier) {
         identifiers.add(Objects.requireNonNull(identifier));
         return self();
     }
@@ -698,12 +703,12 @@
      * @since 0.6
      */
     public B addNamesAndIdentifiers(final IdentifiedObject object) {
-        for (final Identifier id : object.getIdentifiers()) {
+        for (final ReferenceIdentifier id : object.getIdentifiers()) {
             if (isValid(id)) {
                 addIdentifier(id);
             }
         }
-        Identifier id = object.getName();
+        ReferenceIdentifier id = object.getName();
         if (isValid(id)) {
             addName(id);
         }
@@ -727,12 +732,12 @@
      */
     public B addNameAndIdentifier(final Citation authority, final IdentifiedObject object) {
         ArgumentChecks.ensureNonNull("authority", authority);
-        for (final Identifier id : object.getIdentifiers()) {
+        for (final ReferenceIdentifier id : object.getIdentifiers()) {
             if (isValid(id) && authority.equals(id.getAuthority())) {
                 addIdentifier(id);
             }
         }
-        Identifier id = object.getName();
+        ReferenceIdentifier id = object.getName();
         if (isValid(id) && authority.equals(id.getAuthority())) {
             addName(id);
         }
@@ -1041,7 +1046,7 @@
             valueIds   = null;
         } else {
             valueAlias = aliases    .toArray(GenericName[]::new);
-            valueIds   = identifiers.toArray(Identifier[]::new);
+            valueIds   = identifiers.toArray(ReferenceIdentifier[]::new);
         }
         properties.put(IdentifiedObject.ALIAS_KEY,       valueAlias);
         properties.put(IdentifiedObject.IDENTIFIERS_KEY, valueIds);
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 8003fd7..bba206a 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
@@ -90,6 +90,9 @@
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.logging.Logging;
 
+// 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;
@@ -601,12 +604,12 @@
              */
             if (Double.isNaN(roiArea) || maxInsideArea < roiArea) {
                 if (tryDerivedCRS) break;                                               // Do not try twice.
-                final SingleCRS[] derivedCRS = new SingleCRS[sourceCRS.length];
+                final CoordinateReferenceSystem[] derivedCRS = new CoordinateReferenceSystem[sourceCRS.length];
                 for (int i=0; i < derivedCRS.length; i++) {
                     GeographicBoundingBox bbox = null;
                     final CoordinateReferenceSystem crs = sourceCRS[i];
-                    if (crs instanceof DerivedCRS) {
-                        final SingleCRS baseCRS = ((DerivedCRS) crs).getBaseCRS();
+                    if (crs instanceof GeneralDerivedCRS) {
+                        final CoordinateReferenceSystem baseCRS = ((GeneralDerivedCRS) crs).getBaseCRS();
                         bbox = getGeographicBoundingBox(baseCRS);
                         if (bbox == null && bestCRS == null && baseCRS instanceof GeodeticCRS) {
                             bestCRS = baseCRS;      // Fallback to be used if we don't find anything better.
@@ -1298,8 +1301,8 @@
                  * for letting SIS create or associate new ones, which will be two-dimensional now.
                  */
                 if (crs instanceof ProjectedCRS) {
-                    final ProjectedCRS proj = (ProjectedCRS) crs;
-                    final GeodeticCRS  base = (GeodeticCRS) getHorizontalComponent(proj.getBaseCRS());
+                    final ProjectedCRS  proj = (ProjectedCRS) crs;
+                    final GeographicCRS base = (GeographicCRS) getHorizontalComponent(proj.getBaseCRS());
                     Conversion fromBase = proj.getConversionFromBase();
                     fromBase = new DefaultConversion(IdentifiedObjects.getProperties(fromBase),
                             fromBase.getMethod(), null, fromBase.getParameterValues());
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 10eac17..fdcf1d4 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
@@ -53,8 +53,8 @@
 import org.apache.sis.measure.Latitude;
 import org.apache.sis.measure.Units;
 
-// Specific to the geoapi-4.0 branch:
-import org.opengis.referencing.crs.GeodeticCRS;
+// Specific to the main and geoapi-3.1 branches:
+import org.opengis.referencing.crs.GeocentricCRS;
 
 
 /**
@@ -133,14 +133,15 @@
         final boolean ellipsoid  = type.isAssignableFrom(Ellipsoid    .class);
         final boolean datum      = type.isAssignableFrom(GeodeticDatum.class);
         final boolean geographic = type.isAssignableFrom(GeographicCRS.class);
-        final boolean geodetic   = type.isAssignableFrom(GeodeticCRS  .class);
+        @SuppressWarnings("deprecation")
+        final boolean geocentric = type.isAssignableFrom(GeocentricCRS.class);
         final boolean projected  = type.isAssignableFrom(ProjectedCRS .class);
         final Set<String> codes = new LinkedHashSet<>();
         if (pm) codes.add(StandardDefinitions.GREENWICH);
         for (final CommonCRS crs : CommonCRS.values()) {
-            if (ellipsoid) add(codes, crs.ellipsoid);
-            if (datum)     add(codes, crs.datum);
-            if (geodetic)  add(codes, crs.geocentric);
+            if (ellipsoid)  add(codes, crs.ellipsoid);
+            if (datum)      add(codes, crs.datum);
+            if (geocentric) add(codes, crs.geocentric);
             if (geographic) {
                 add(codes, crs.geographic);
                 add(codes, crs.geo3D);
@@ -265,6 +266,7 @@
      * 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/EllipsoidalHeightSeparator.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/EllipsoidalHeightSeparator.java
index d03a744..4b70ced 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/EllipsoidalHeightSeparator.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/EllipsoidalHeightSeparator.java
@@ -39,6 +39,9 @@
 import org.apache.sis.referencing.factory.GeodeticObjectFactory;
 import static org.apache.sis.referencing.privy.ReferencingUtilities.getPropertiesForModifiedCRS;
 
+// Specific to the main and geoapi-3.1 branches:
+import org.opengis.referencing.crs.GeographicCRS;
+
 
 /**
  * Helper class for separating the ellipsoidal height from the horizontal part of a CRS.
@@ -128,9 +131,9 @@
          * we also need to reduce the number of dimensions in the base CRS and in the conversion.
          */
         if (crs instanceof ProjectedCRS) {
-            GeodeticCRS baseCRS = ((ProjectedCRS) crs).getBaseCRS();
+            GeographicCRS baseCRS = ((ProjectedCRS) crs).getBaseCRS();
             if (ReferencingUtilities.getDimension(baseCRS) != 2) {
-                baseCRS = (GeodeticCRS) separate(baseCRS);
+                baseCRS = (GeographicCRS) separate(baseCRS);
             }
             Conversion projection = ((ProjectedCRS) crs).getConversionFromBase();
             /*
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 491c974..18016e8 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
@@ -38,6 +38,9 @@
 import org.apache.sis.io.wkt.ElementKind;
 import static org.apache.sis.util.collection.Containers.property;
 
+// Specific to the main and geoapi-3.1 branches:
+import org.opengis.referencing.ReferenceIdentifier;
+
 
 /**
  * Immutable value uniquely identifying an object within a namespace, together with a version.
@@ -108,7 +111,7 @@
  * @since 1.0
  */
 @TitleProperty(name = "code")
-public class ImmutableIdentifier extends FormattableObject implements Identifier, Serializable {
+public class ImmutableIdentifier extends FormattableObject implements ReferenceIdentifier, Serializable {
     /**
      * For cross-version compatibility.
      */
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/NameIterator.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/NameIterator.java
index b29411d..72788ec 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/NameIterator.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/NameIterator.java
@@ -26,13 +26,13 @@
 import org.apache.sis.referencing.privy.NilReferencingObject;
 import static org.apache.sis.util.privy.Strings.appendUnicodeIdentifier;
 
-// Specific to the geoapi-4.0 branch:
-import org.opengis.metadata.Identifier;
+// Specific to the main and geoapi-3.1 branches:
+import org.opengis.referencing.ReferenceIdentifier;
 
 
 /**
  * An iterator over the {@linkplain IdentifiedObject#getName() name} of an identified object followed by
- * {@linkplain IdentifiedObject#getAlias() aliases} which are instance of {@link Identifier}.
+ * {@linkplain IdentifiedObject#getAlias() aliases} which are instance of {@link ReferenceIdentifier}.
  * This iterator is used for {@link AbstractIdentifiedObject} XML marshalling because GML merges the name
  * and aliases in a single {@code <gml:name>} property. However, this iterator is useful only if the aliases
  * are instances of {@link NamedIdentifier}, or any other implementation which is both a name and an identifier.
@@ -41,11 +41,11 @@
  *
  * @author  Martin Desruisseaux (Geomatys)
  */
-final class NameIterator implements Iterator<Identifier> {
+final class NameIterator implements Iterator<ReferenceIdentifier> {
     /**
      * The next element to return, or {@code null} if we reached the end of iteration.
      */
-    private Identifier next;
+    private ReferenceIdentifier next;
 
     /**
      * An iterator over the aliases.
@@ -67,7 +67,7 @@
     /**
      * Returns {@code true} if the given identifier is null or the {@link NilReferencingObject#UNNAMED} instance.
      */
-    static boolean isUnnamed(final Identifier name) {
+    static boolean isUnnamed(final ReferenceIdentifier name) {
         return (name == null) || (name == NilReferencingObject.UNNAMED);
     }
 
@@ -86,12 +86,12 @@
      * will be used only by JAXB, which is presumed checking for {@link #hasNext()} correctly.
      */
     @Override
-    public Identifier next() {
-        final Identifier n = next;
+    public ReferenceIdentifier next() {
+        final ReferenceIdentifier n = next;
         while (alias.hasNext()) {
             final GenericName c = alias.next();
-            if (c instanceof Identifier) {
-                next = (Identifier) c;
+            if (c instanceof ReferenceIdentifier) {
+                next = (ReferenceIdentifier) c;
                 return n;
             }
         }
@@ -145,8 +145,8 @@
      *
      * @see AbstractIdentifiedObject#getID()
      */
-    static String getID(final IdentifiedObject object, final Identifier name,
-            final Collection<? extends GenericName> alias, final Collection<? extends Identifier> identifiers)
+    static String getID(final IdentifiedObject object, final ReferenceIdentifier name,
+            final Collection<? extends GenericName> alias, final Collection<? extends ReferenceIdentifier> identifiers)
     {
         final Context context = Context.current();
         String candidate = Context.getObjectID(context, object);
@@ -157,7 +157,7 @@
              * if we found no suitable ID, then we will use the primary name as a last resort.
              */
             if (identifiers != null) {
-                for (final Identifier identifier : identifiers) {
+                for (final ReferenceIdentifier identifier : identifiers) {
                     if (appendUnicodeIdentifier(id, '-', identifier.getCodeSpace(), "", true) |    // Really |, not ||
                         appendUnicodeIdentifier(id, '-', NameMeaning.toObjectType(object.getClass()), "", false) |
                         appendUnicodeIdentifier(id, '-', identifier.getCode(), "", true))
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 a0fd578..a59869d 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
@@ -37,6 +37,9 @@
 import org.apache.sis.metadata.iso.citation.Citations;
 import org.apache.sis.util.iso.DefaultNameFactory;
 
+// Specific to the main and geoapi-3.1 branches:
+import org.opengis.referencing.ReferenceIdentifier;
+
 
 /**
  * An identification of a CRS object which is both a {@link Identifier} and a {@link GenericName}.
@@ -91,7 +94,7 @@
  *
  * @since 0.4
  */
-public class NamedIdentifier extends ImmutableIdentifier implements GenericName {
+public class NamedIdentifier extends ImmutableIdentifier implements GenericName, ReferenceIdentifier {
     /**
      * Serial number for inter-operability with different versions.
      */
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 8976492..5a3d432 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
@@ -31,14 +31,14 @@
 import org.apache.sis.util.privy.AbstractMap;
 import org.apache.sis.referencing.privy.CoordinateOperations;
 
+// Specific to the main and geoapi-3.1 branches:
+import org.opengis.referencing.ReferenceSystem;
+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 geoapi-4.0 branch:
-import org.opengis.util.InternationalString;
-import org.opengis.metadata.Identifier;
-import org.opengis.metadata.extent.Extent;
-
 
 /**
  * An immutable map fetching all properties from the specified identified object.
@@ -131,27 +131,33 @@
         if ((excludeMask & (1 << key)) == 0) {
             switch (key) {
                 case 0: return         object.getName();                                // NAME_KEY
-                case 1: return toArray(object.getIdentifiers(), Identifier[]::new);     // IDENTIFIERS_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().orElse(null);                // REMARKS_KEY
+                case 4: return         object.getRemarks();                             // REMARKS_KEY
                 case 5: {   // SCOPE_KEY
-                    for (final ObjectDomain domain : object.getDomains()) {
-                        InternationalString scope = domain.getScope();
-                        if (scope != null) return scope;
+                    if (object instanceof ReferenceSystem) {
+                        return ((ReferenceSystem) object).getScope();
+                    } else if (object instanceof Datum) {
+                        return ((Datum) object).getScope();
+                    } else if (object instanceof CoordinateOperation) {
+                        return ((CoordinateOperation) object).getScope();
                     }
                     break;
                 }
                 case 6: {   // DOMAIN_OF_VALIDITY_KEY
-                    for (final ObjectDomain domain : object.getDomains()) {
-                        Extent extent = domain.getDomainOfValidity();
-                        if (extent != null) return extent;
+                    if (object instanceof ReferenceSystem) {
+                        return ((ReferenceSystem) object).getDomainOfValidity();
+                    } else if (object instanceof Datum) {
+                        return ((Datum) object).getDomainOfValidity();
+                    } else if (object instanceof CoordinateOperation) {
+                        return ((CoordinateOperation) object).getDomainOfValidity();
                     }
                     break;
                 }
                 case 7: {   // OPERATION_VERSION_KEY
                     if (object instanceof CoordinateOperation) {
-                        return ((CoordinateOperation) object).getOperationVersion().orElse(null);
+                        return ((CoordinateOperation) object).getOperationVersion();
                     }
                     break;
                 }
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 d5b763c..6af80ae 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
@@ -76,6 +76,9 @@
 // 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;
+
 
 /**
  * Definitions of referencing objects identified by the {@link CommonCRS} enumeration values.
@@ -123,7 +126,7 @@
         c.setTitle(Vocabulary.formatInternational(Vocabulary.Keys.SubsetOf_1, Constants.EPSG));
         c.setEdition(new SimpleInternationalString(StandardDefinitions.VERSION));
         c.getPresentationForms().add(PresentationForm.DOCUMENT_DIGITAL);
-        c.getOtherCitationDetails().add(NOTICE);
+        c.setOtherCitationDetails(NOTICE);
         c.transitionTo(DefaultCitation.State.FINAL);
         AUTHORITY = c;
     }
@@ -347,7 +350,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), null);
+        return new DefaultVerticalDatum(properties(code, name, alias, true), (RealizationMethod) null);
     }
 
     /**
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 f556b0b..2e03edc 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
@@ -42,13 +42,13 @@
 import org.apache.sis.util.ComparisonMode;
 import org.apache.sis.util.resources.Errors;
 
+// Specific to the main and geoapi-3.1 branches:
+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 geoapi-4.0 branch:
-import org.opengis.coordinate.MismatchedDimensionException;
-import org.opengis.referencing.crs.DerivedCRS;
-
 
 /**
  * Coordinate reference system, defined by a {@linkplain AbstractCS coordinate system}
@@ -251,7 +251,8 @@
      *       {@link org.opengis.referencing.crs.GeographicCRS subtype}),
      *       {@link org.opengis.referencing.crs.VerticalCRS},
      *       {@link org.opengis.referencing.crs.TemporalCRS},
-     *       {@link org.opengis.referencing.crs.EngineeringCRS} or
+     *       {@link org.opengis.referencing.crs.EngineeringCRS},
+     *       {@link org.opengis.referencing.crs.ImageCRS} or
      *       {@link org.apache.sis.referencing.cs.DefaultCompoundCS},
      *       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,
@@ -475,14 +476,15 @@
      * {@link AbstractDerivedCRS}. In such case, the coordinate system axes shall not be formatted.
      *
      * <p>This method should return {@code true} when {@code this} CRS is the value returned by
-     * {@link DerivedCRS#getBaseCRS()} (typically {@link AbstractDerivedCRS#getBaseCRS()}).
+     * {@link GeneralDerivedCRS#getBaseCRS()} (typically {@link AbstractDerivedCRS#getBaseCRS()}).
      * Since the base CRS is the only CRS enclosed in derived CRS, we should have no ambiguity
      * (assuming that the user did not created some weird subclass).</p>
      *
      * <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 DerivedCRS;
+        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 cbc2430..fb1ab0c 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
@@ -44,17 +44,19 @@
 import org.apache.sis.util.ComparisonMode;
 import org.apache.sis.util.resources.Errors;
 
+// 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 geoapi-4.0 branch:
-import org.opengis.referencing.crs.DerivedCRS;
-
 
 /**
  * A coordinate reference system that is defined by its coordinate conversion from another CRS.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
+ *
+ * @param <C>  the conversion type, either {@code Conversion} or {@code Projection}.
  */
 @XmlType(name = "AbstractGeneralDerivedCRSType")
 @XmlRootElement(name = "AbstractGeneralDerivedCRS")
@@ -62,7 +64,8 @@
     DefaultDerivedCRS.class,
     DefaultProjectedCRS.class
 })
-abstract class AbstractDerivedCRS extends AbstractCRS implements DerivedCRS {
+@SuppressWarnings("deprecation")
+abstract class AbstractDerivedCRS<C extends Conversion> extends AbstractCRS implements GeneralDerivedCRS {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -70,7 +73,7 @@
 
     /**
      * The conversion from the {@linkplain #getBaseCRS() base CRS} to this CRS.
-     * The base CRS of this {@code DerivedCRS} is {@link Conversion#getSourceCRS()}.
+     * The base CRS of this {@code GeneralDerivedCRS} is {@link Conversion#getSourceCRS()}.
      *
      * <p><b>Consider this field as final!</b>
      * This field is modified only at unmarshalling time by {@link #setConversionFromBase(Conversion)}</p>
@@ -78,7 +81,7 @@
      * @see #getConversionFromBase()
      */
     @SuppressWarnings("serial")         // Most SIS implementations are serializable.
-    private Conversion conversionFromBase;
+    private C conversionFromBase;
 
     /**
      * Creates a derived CRS from a defining conversion.
@@ -113,7 +116,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.
      */
-    AbstractDerivedCRS(final AbstractDerivedCRS original, final AbstractCS derivedCS) {
+    AbstractDerivedCRS(final AbstractDerivedCRS<C> original, final AbstractCS derivedCS) {
         super(original, null, derivedCS);
         final Conversion conversion = original.conversionFromBase;
         conversionFromBase = createConversionFromBase(null, (SingleCRS) conversion.getSourceCRS(), conversion);
@@ -136,8 +139,8 @@
         ArgumentChecks.ensureNonNull("baseCRS", baseCRS);
         ArgumentChecks.ensureNonNull("method", method);
         ArgumentChecks.ensureNonNull("baseToDerived", baseToDerived);
-        conversionFromBase = new DefaultConversion(ConversionKeys.unprefix(properties),
-                baseCRS, this, interpolationCRS, method, baseToDerived);
+        conversionFromBase = (C) new DefaultConversion(   // Cast to (C) is valid only for DefaultDerivedCRS.
+                ConversionKeys.unprefix(properties), baseCRS, this, interpolationCRS, method, baseToDerived);
     }
 
     /**
@@ -149,9 +152,9 @@
      *
      * @param  crs  the coordinate reference system to copy.
      */
-    AbstractDerivedCRS(final DerivedCRS crs) {
+    AbstractDerivedCRS(final GeneralDerivedCRS crs) {
         super(crs);
-        conversionFromBase = createConversionFromBase(null, crs.getBaseCRS(), crs.getConversionFromBase());
+        conversionFromBase = createConversionFromBase(null, (SingleCRS) crs.getBaseCRS(), crs.getConversionFromBase());
     }
 
     /**
@@ -163,13 +166,13 @@
      * instance is advanced enough for allowing the {@code getCoordinateSystem()} method to execute.
      * Subclasses should make their {@code getCoordinateSystem()} method final for better guarantees.</p>
      */
-    private Conversion createConversionFromBase(final Map<String,?> properties, final SingleCRS baseCRS, final Conversion conversion) {
+    private C createConversionFromBase(final Map<String,?> properties, final SingleCRS baseCRS, final Conversion conversion) {
         MathTransformFactory factory = null;
         if (properties != null) {
             factory = (MathTransformFactory) properties.get(ReferencingFactoryContainer.MT_FACTORY);
         }
         try {
-            return DefaultConversion.castOrCopy(conversion).specialize(baseCRS, this, factory);
+            return getConversionType().cast(DefaultConversion.castOrCopy(conversion).specialize(baseCRS, this, factory));
         } catch (FactoryException e) {
             throw new IllegalArgumentException(Errors.forProperties(properties).getString(
                     Errors.Keys.IllegalArgumentValue_2, "conversion", conversion.getName()), e);
@@ -177,7 +180,16 @@
     }
 
     /**
-     * Returns the datum of the base CRS.
+     * Returns the type of conversion associated to this {@code AbstractDerivedCRS}.
+     *
+     * <p><b>WARNING:</b> this method is invoked (indirectly) at construction time.
+     * Consequently, it shall return a constant value - this method is not allowed to
+     * depend on the object state.</p>
+     */
+    abstract Class<C> getConversionType();
+
+    /**
+     * Returns the datum of the {@linkplain #getBaseCRS() base CRS}.
      *
      * @return the datum of the base CRS.
      */
@@ -191,7 +203,7 @@
      */
     @Override
     @XmlElement(name = "conversion", required = true)
-    public Conversion getConversionFromBase() {
+    public C getConversionFromBase() {
         return conversionFromBase;
     }
 
@@ -228,7 +240,7 @@
                 return Utilities.deepEquals(
                         strict ? conversionFromBase : getConversionFromBase(),
                         strict ? ((AbstractDerivedCRS) object).conversionFromBase
-                               :  ((DerivedCRS) object).getConversionFromBase(), mode);
+                               :  ((GeneralDerivedCRS) object).getConversionFromBase(), mode);
             } finally {
                 Semaphores.clear(Semaphores.CONVERSION_AND_CRS);
             }
@@ -284,7 +296,7 @@
      * At this state, the given conversion has null {@code sourceCRS} and {@code targetCRS}.
      * Those CRS will be set later, in {@link #afterUnmarshal(Unmarshaller, Object)}.
      */
-    private void setConversionFromBase(final Conversion conversion) {
+    private void setConversionFromBase(final C conversion) {
         if (conversionFromBase == null) {
             conversionFromBase = conversion;
         } else {
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 632e308..1894051 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
@@ -105,7 +105,7 @@
     "coordinateSystem"
 })
 @XmlRootElement(name = "DerivedCRS")
-public class DefaultDerivedCRS extends AbstractDerivedCRS implements DerivedCRS {
+public class DefaultDerivedCRS extends AbstractDerivedCRS<Conversion> implements DerivedCRS {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -368,7 +368,7 @@
         if (object == null || object instanceof DefaultDerivedCRS) {
             return (DefaultDerivedCRS) object;
         } else {
-            final String type = getTypeKeyword(null, object.getBaseCRS(), object.getCoordinateSystem());
+            final String type = getTypeKeyword(null, (SingleCRS) object.getBaseCRS(), object.getCoordinateSystem());
             if (type != null) switch (type) {
                 case WKTKeywords.GeodeticCRS:    return new Geodetic   (object);
                 case WKTKeywords.VerticalCRS:    return new Vertical   (object);
@@ -381,6 +381,15 @@
     }
 
     /**
+     * Returns the type of conversion associated to this {@code DefaultDerivedCRS}.
+     * Must be a hard-coded, constant value (not dependent on object state).
+     */
+    @Override
+    final Class<Conversion> getConversionType() {
+        return Conversion.class;
+    }
+
+    /**
      * Returns the GeoAPI interface implemented by this class.
      * The SIS implementation returns {@code DerivedCRS.class}.
      *
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 284144c..940daa1 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
@@ -327,7 +327,8 @@
     @XmlElement(name="sphericalCS")   private SphericalCS   getSphericalCS()   {return getCoordinateSystem(SphericalCS  .class);}
 
     // Types that do not exist anymore in lastest ISO 19111 standard.
-    @XmlElement(name="userDefinedCS") private DefaultUserDefinedCS getUserDefinedCS() {return getCoordinateSystem(DefaultUserDefinedCS.class);}
+    @Deprecated(since = "1.5")
+    @XmlElement(name="userDefinedCS") private UserDefinedCS getUserDefinedCS() {return getCoordinateSystem(UserDefinedCS.class);}
 
     /**
      * Invoked by JAXB at unmarshalling time.
@@ -340,14 +341,16 @@
     private void setSphericalCS  (final SphericalCS   cs) {super.setCoordinateSystem("sphericalCS",   cs);}
 
     // Types that do not exist anymore in lastest ISO 19111 standard.
-    private void setUserDefinedCS(final DefaultUserDefinedCS cs) {super.setCoordinateSystem("userDefinedCS", cs);}
+    @Deprecated(since = "1.5")
+    private void setUserDefinedCS(final UserDefinedCS cs) {super.setCoordinateSystem("userDefinedCS", cs);}
 
     /**
      * 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, DefaultUserDefinedCS.class
+        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 09c4219..6e3c3cb 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
@@ -29,6 +29,9 @@
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.io.wkt.Formatter;
 
+// Specific to the main and geoapi-3.1 branches:
+import org.opengis.referencing.crs.GeocentricCRS;
+
 
 /**
  * A 2- or 3-dimensional coordinate reference system with the origin at the approximate centre of mass of the earth.
@@ -78,7 +81,8 @@
  * @since 0.4
  */
 @XmlTransient
-public class DefaultGeocentricCRS extends DefaultGeodeticCRS {
+@SuppressWarnings("deprecation")
+public class DefaultGeocentricCRS extends DefaultGeodeticCRS implements GeocentricCRS {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -202,13 +206,13 @@
 
     /**
      * Returns the GeoAPI interface implemented by this class.
-     * The SIS implementation returns {@code GeodeticCRS.class}.
+     * The SIS implementation returns {@code GeocentricCRS.class}.
      *
-     * @return {@code GeodeticCRS.class} or a user-defined sub-interface.
+     * @return {@code GeocentricCRS.class} or a user-defined sub-interface.
      */
     @Override
-    public Class<? extends GeodeticCRS> getInterface() {
-        return super.getInterface();
+    public Class<? extends GeocentricCRS> getInterface() {
+        return GeocentricCRS.class;
     }
 
     /**
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 e9e58b4..1a46661 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
@@ -30,8 +30,9 @@
 import org.apache.sis.metadata.privy.ImplementationHelper;
 import org.apache.sis.io.wkt.Formatter;
 
-// Specific to the geoapi-4.0 branch:
-import org.apache.sis.referencing.datum.DefaultImageDatum;
+// Specific to the main and geoapi-3.1 branches:
+import org.opengis.referencing.crs.ImageCRS;
+import org.opengis.referencing.datum.ImageDatum;
 
 
 /**
@@ -69,7 +70,7 @@
     "datum"
 })
 @XmlRootElement(name = "ImageCRS")
-public final class DefaultImageCRS extends AbstractCRS {
+public final class DefaultImageCRS extends AbstractCRS implements ImageCRS {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -83,7 +84,8 @@
      *
      * @see #getDatum()
      */
-    private DefaultImageDatum datum;
+    @SuppressWarnings("serial")     // Most SIS implementations are serializable.
+    private ImageDatum datum;
 
     /**
      * Creates a coordinate reference system from the given properties, datum and coordinate system.
@@ -123,10 +125,12 @@
      * @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#createImageCRS(Map, ImageDatum, AffineCS)
      */
     public DefaultImageCRS(final Map<String,?> properties,
-                           final DefaultImageDatum datum,
-                           final AffineCS cs)
+                           final ImageDatum    datum,
+                           final AffineCS      cs)
     {
         super(properties, cs);
         this.datum = Objects.requireNonNull(datum);
@@ -142,13 +146,60 @@
     }
 
     /**
+     * Constructs a new coordinate reference system 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  crs  the coordinate reference system to copy.
+     *
+     * @see #castOrCopy(ImageCRS)
+     */
+    protected DefaultImageCRS(final ImageCRS 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 DefaultImageCRS castOrCopy(final ImageCRS object) {
+        return (object == null) || (object instanceof DefaultImageCRS)
+                ? (DefaultImageCRS) object : new DefaultImageCRS(object);
+    }
+
+    /**
+     * Returns the GeoAPI interface implemented by this class.
+     * The SIS implementation returns {@code ImageCRS.class}.
+     *
+     * <h4>Note for implementers</h4>
+     * Subclasses usually do not need to override this method since GeoAPI does not define {@code ImageCRS}
+     * sub-interface. Overriding possibility is left mostly for implementers who wish to extend GeoAPI with
+     * their own set of interfaces.
+     *
+     * @return {@code ImageCRS.class} or a user-defined sub-interface.
+     */
+    @Override
+    public Class<? extends ImageCRS> getInterface() {
+        return ImageCRS.class;
+    }
+
+    /**
      * Returns the datum.
      *
      * @return the datum.
      */
     @Override
     @XmlElement(name = "imageDatum", required = true)
-    public DefaultImageDatum getDatum() {
+    public ImageDatum getDatum() {
         return datum;
     }
 
@@ -238,7 +289,7 @@
      *
      * @see #getDatum()
      */
-    private void setDatum(final DefaultImageDatum value) {
+    private void setDatum(final ImageDatum value) {
         if (datum == null) {
             datum = value;
         } else {
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 2ae3604..df474db 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
@@ -40,6 +40,10 @@
 import org.apache.sis.util.ComparisonMode;
 import org.apache.sis.util.Workaround;
 
+// Specific to the main and geoapi-3.1 branches:
+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;
 
@@ -69,11 +73,11 @@
  * @since 0.6
  */
 @XmlType(name = "ProjectedCRSType", propOrder = {
-    "baseGeodeticCRS",
+    "baseCRS",
     "coordinateSystem"
 })
 @XmlRootElement(name = "ProjectedCRS")
-public class DefaultProjectedCRS extends AbstractDerivedCRS implements ProjectedCRS {
+public class DefaultProjectedCRS extends AbstractDerivedCRS<Projection> implements ProjectedCRS {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -127,10 +131,10 @@
      * @throws MismatchedDimensionException if the source and target dimensions of {@code baseToDerived}
      *         do not match the dimensions of {@code base} and {@code derivedCS} respectively.
      *
-     * @see org.apache.sis.referencing.factory.GeodeticObjectFactory#createProjectedCRS(Map, GeodeticCRS, Conversion, CartesianCS)
+     * @see org.apache.sis.referencing.factory.GeodeticObjectFactory#createProjectedCRS(Map, GeographicCRS, Conversion, CartesianCS)
      */
     public DefaultProjectedCRS(final Map<String,?> properties,
-                               final GeodeticCRS   baseCRS,
+                               final GeographicCRS baseCRS,
                                final Conversion    conversion,
                                final CartesianCS   derivedCS)
             throws MismatchedDimensionException
@@ -193,6 +197,15 @@
     }
 
     /**
+     * Returns the type of conversion associated to this {@code DefaultProjectedCRS}.
+     * Must be a hard-coded, constant value (not dependent on object state).
+     */
+    @Override
+    final Class<Projection> getConversionType() {
+        return Projection.class;
+    }
+
+    /**
      * Returns the GeoAPI interface implemented by this class.
      * The SIS implementation returns {@code ProjectedCRS.class}.
      *
@@ -227,9 +240,10 @@
      * @return the base coordinate reference system, which must be geographic.
      */
     @Override
-    public GeodeticCRS getBaseCRS() {
-        final Conversion projection = super.getConversionFromBase();
-        return (projection != null) ? (GeodeticCRS) projection.getSourceCRS() : null;
+    @XmlElement(name = "baseGeodeticCRS", required = true)        // Note: older GML version used "baseGeographicCRS".
+    public GeographicCRS getBaseCRS() {
+        final Projection projection = super.getConversionFromBase();
+        return (projection != null) ? (GeographicCRS) projection.getSourceCRS() : null;
     }
 
     /**
@@ -249,7 +263,7 @@
      * @return the map projection from base CRS to this CRS.
      */
     @Override
-    public Conversion getConversionFromBase() {
+    public Projection getConversionFromBase() {
         return super.getConversionFromBase();
     }
 
@@ -444,19 +458,12 @@
     }
 
     /**
-     * Used by JAXB only (invoked by reflection). We do not use adapter because,
-     * for an unknown reason, doing so cause an infinite loop in Glassfish JAXB.
-     */
-    @XmlElement(name = "baseGeodeticCRS", required = true)    // Note: older GML version used "baseGeographicCRS".
-    private SC_GeodeticCRS getBaseGeodeticCRS() {
-        return new SC_GeodeticCRS(getBaseCRS());
-    }
-
-    /**
      * Used by JAXB only (invoked by reflection).
+     *
+     * @see #getBaseCRS()
      */
-    private void setBaseGeodeticCRS(final SC_GeodeticCRS crs) {
-        setBaseCRS("baseGeodeticCRS", crs.getElement());
+    private void setBaseCRS(final GeographicCRS crs) {
+        setBaseCRS("baseGeodeticCRS", crs);
     }
 
     /**
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 0c3a557..5381ff1 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
@@ -45,6 +45,9 @@
 import static org.apache.sis.util.privy.Constants.NANOS_PER_SECOND;
 import static org.apache.sis.util.privy.Constants.MILLIS_PER_SECOND;
 
+// Specific to the main and geoapi-3.1 branches:
+import org.apache.sis.util.privy.TemporalDate;
+
 
 /**
  * A 1-dimensional coordinate reference system used for the recording of time.
@@ -225,7 +228,7 @@
      */
     private void initializeConverter() {
         toSeconds = getUnit().getConverterTo(Units.SECOND);
-        final Temporal t = datum.getOrigin();
+        final Temporal t = TemporalDate.toTemporal(datum.getOrigin());
         origin = t.getLong(ChronoField.INSTANT_SECONDS);
         int r = t.get(ChronoField.NANO_OF_SECOND);
         if (r != 0) {
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/ExplicitParameters.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/ExplicitParameters.java
index 65a7624..5984372 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/ExplicitParameters.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/ExplicitParameters.java
@@ -59,7 +59,7 @@
     /**
      * Creates a new temporary {@code Conversion} elements for the parameters of the given CRS.
      */
-    ExplicitParameters(final AbstractDerivedCRS crs, final String keyword) {
+    ExplicitParameters(final AbstractDerivedCRS<?> crs, final String keyword) {
         conversion = crs.getConversionFromBase();
         final Datum datum = crs.getDatum();
         ellipsoid = (datum instanceof GeodeticDatum) ? ((GeodeticDatum) datum).getEllipsoid() : null;
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/SC_GeodeticCRS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/SC_GeographicCRS.java
similarity index 73%
rename from endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/SC_GeodeticCRS.java
rename to endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/SC_GeographicCRS.java
index cf7567b..d4d3fc7 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/SC_GeodeticCRS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/SC_GeographicCRS.java
@@ -17,12 +17,12 @@
 package org.apache.sis.referencing.crs;
 
 import jakarta.xml.bind.annotation.XmlElement;
-import org.opengis.referencing.crs.GeodeticCRS;
+import org.opengis.referencing.crs.GeographicCRS;
 import org.apache.sis.xml.bind.gco.PropertyType;
 
 
 /**
- * JAXB adapter for {@link GeodeticCRS}, in order to integrate the value in an element
+ * JAXB adapter for {@link GeographicCRS}, in order to integrate the value in an element
  * complying with OGC/ISO standard.
  *
  * <p><b>Note:</b> JAXB adapters are usually declared in the {@link org.apache.sis.xml.bind.referencing} package,
@@ -30,11 +30,11 @@
  *
  * @author  Martin Desruisseaux (Geomatys)
  */
-final class SC_GeodeticCRS extends PropertyType<SC_GeodeticCRS, GeodeticCRS> {
+final class SC_GeographicCRS extends PropertyType<SC_GeographicCRS, GeographicCRS> {
     /**
      * Empty constructor for JAXB only.
      */
-    public SC_GeodeticCRS() {
+    public SC_GeographicCRS() {
     }
 
     /**
@@ -42,30 +42,30 @@
      * This method is indirectly invoked by the private constructor
      * below, so it shall not depend on the state of this object.
      *
-     * @return {@code GeodeticCRS.class}
+     * @return {@code GeographicCRS.class}
      */
     @Override
-    protected Class<GeodeticCRS> getBoundType() {
-        return GeodeticCRS.class;
+    protected Class<GeographicCRS> getBoundType() {
+        return GeographicCRS.class;
     }
 
     /**
      * Constructor for the {@link #wrap} method only.
      */
-    SC_GeodeticCRS(final GeodeticCRS crs) {
-        super(crs);
+    private SC_GeographicCRS(final GeographicCRS cs) {
+        super(cs);
     }
 
     /**
      * Invoked by {@link PropertyType} at marshalling time for wrapping the given value
      * in a {@code <gml:GeodeticCRS>} XML element.
      *
-     * @param  crs  the element to marshal.
+     * @param  cs  the element to marshal.
      * @return a {@code PropertyType} wrapping the given the element.
      */
     @Override
-    protected SC_GeodeticCRS wrap(final GeodeticCRS crs) {
-        return new SC_GeodeticCRS(crs);
+    protected SC_GeographicCRS wrap(final GeographicCRS cs) {
+        return new SC_GeographicCRS(cs);
     }
 
     /**
@@ -77,8 +77,7 @@
      */
     @XmlElement(name = "GeodeticCRS")
     public DefaultGeodeticCRS getElement() {
-        @SuppressWarnings("LocalVariableHidesMemberVariable")
-        final GeodeticCRS metadata = this.metadata;
+        final GeographicCRS metadata = this.metadata;
         if (metadata == null || metadata instanceof DefaultGeodeticCRS) {
             return (DefaultGeodeticCRS) metadata;
         } else {
@@ -89,9 +88,13 @@
     /**
      * Invoked by JAXB at unmarshalling time for storing the result temporarily.
      *
-     * @param  crs  the unmarshalled element.
+     * @param  cs  the unmarshalled element.
      */
-    public void setElement(final DefaultGeodeticCRS crs) {
-        metadata = crs;
+    public void setElement(final DefaultGeodeticCRS cs) {
+        if (cs == null || cs instanceof GeographicCRS) {
+            metadata = (GeographicCRS) cs;
+        } else {
+            metadata = new DefaultGeographicCRS(cs);
+        }
     }
 }
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/SubTypes.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/SubTypes.java
index e02e655..3e82a45 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/SubTypes.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/SubTypes.java
@@ -31,6 +31,9 @@
 import org.opengis.referencing.cs.SphericalCS;
 import org.apache.sis.referencing.cs.AxesConvention;
 
+// Specific to the main and geoapi-3.1 branches:
+import org.opengis.referencing.crs.ImageCRS;
+
 
 /**
  * Implementation of {@link AbstractCRS} methods that require knowledge about subclasses.
@@ -131,6 +134,9 @@
         if (object instanceof EngineeringCRS) {
             return DefaultEngineeringCRS.castOrCopy((EngineeringCRS) object);
         }
+        if (object instanceof ImageCRS) {
+            return DefaultImageCRS.castOrCopy((ImageCRS) object);
+        }
         if (object instanceof CompoundCRS) {
             return DefaultCompoundCRS.castOrCopy((CompoundCRS) object);
         }
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/package-info.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/package-info.java
index f4dd716..d71a18e 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/package-info.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/package-info.java
@@ -114,7 +114,7 @@
     @XmlJavaTypeAdapter(CS_UserDefinedCS.class),
     @XmlJavaTypeAdapter(CS_VerticalCS.class),
     @XmlJavaTypeAdapter(CC_Conversion.class),
-//  @XmlJavaTypeAdapter(SC_GeodeticCRS.class),      // Causes an infinite loop. Replaced by direct instantiation.
+    @XmlJavaTypeAdapter(SC_GeographicCRS.class),
     @XmlJavaTypeAdapter(StringAdapter.class),
     @XmlJavaTypeAdapter(InternationalStringConverter.class)
 })
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 b336ebf..3ef74dd 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
@@ -54,8 +54,8 @@
 import org.apache.sis.io.wkt.Formatter;
 import static org.apache.sis.util.ArgumentChecks.*;
 
-// Specific to the geoapi-4.0 branch:
-import org.opengis.coordinate.MismatchedDimensionException;
+// Specific to the main and geoapi-3.1 branches:
+import org.opengis.geometry.MismatchedDimensionException;
 
 
 /**
@@ -199,6 +199,7 @@
      * @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];
@@ -230,7 +231,7 @@
              * more than one time axis. Such case happen in meteorological models.
              */
             final AxisDirection dir = AxisDirections.absolute(direction);
-            if (dir != AxisDirection.UNSPECIFIED && !AxisDirections.isLegacyOther(dir)) {
+            if (dir != AxisDirection.UNSPECIFIED && dir != AxisDirection.OTHER) {
                 for (int j=i; --j>=0;) {
                     final AxisDirection other = axes[j].getDirection();
                     final AxisDirection abs = AxisDirections.absolute(other);
@@ -325,8 +326,9 @@
      *       {@link org.opengis.referencing.cs.CylindricalCS},
      *       {@link org.opengis.referencing.cs.PolarCS},
      *       {@link org.opengis.referencing.cs.LinearCS},
-     *       {@link org.opengis.referencing.cs.VerticalCS} or
-     *       {@link org.opengis.referencing.cs.TimeCS}.
+     *       {@link org.opengis.referencing.cs.VerticalCS},
+     *       {@link org.opengis.referencing.cs.TimeCS} or
+     *       {@link org.opengis.referencing.cs.UserDefinedCS},
      *       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>
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 304f1a3..525b514 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
@@ -21,6 +21,9 @@
 import jakarta.xml.bind.annotation.XmlRootElement;
 import org.opengis.referencing.cs.CoordinateSystemAxis;
 
+// Specific to the main and geoapi-3.1 branches:
+import org.opengis.referencing.cs.UserDefinedCS;
+
 
 /**
  * A 2- or 3-dimensional coordinate system for any combination of coordinate axes not covered by other CS types.
@@ -50,7 +53,7 @@
 @Deprecated(since="1.5", forRemoval=true)   // Actually to be moved to an internal package for GML and WKT purposes.
 @XmlType(name = "UserDefinedCSType")
 @XmlRootElement(name = "UserDefinedCS")
-public final class DefaultUserDefinedCS extends AbstractCS {
+public final class DefaultUserDefinedCS extends AbstractCS implements UserDefinedCS {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -130,6 +133,52 @@
     }
 
     /**
+     * Creates a new coordinate system 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  original  the coordinate system to copy.
+     *
+     * @see #castOrCopy(UserDefinedCS)
+     */
+    protected DefaultUserDefinedCS(final UserDefinedCS 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 DefaultUserDefinedCS castOrCopy(final UserDefinedCS object) {
+        return (object == null) || (object instanceof DefaultUserDefinedCS)
+                ? (DefaultUserDefinedCS) object : new DefaultUserDefinedCS(object);
+    }
+
+    /**
+     * Returns the GeoAPI interface implemented by this class.
+     * The SIS implementation returns {@code UserDefinedCS.class}.
+     *
+     * <h4>Note for implementers</h4>
+     * Subclasses usually do not need to override this method since GeoAPI does not define {@code UserDefinedCS}
+     * sub-interface. Overriding possibility is left mostly for implementers who wish to extend GeoAPI with their
+     * own set of interfaces.
+     *
+     * @return {@code UserDefinedCS.class} or a user-defined sub-interface.
+     */
+    @Override
+    public Class<? extends UserDefinedCS> getInterface() {
+        return UserDefinedCS.class;
+    }
+
+    /**
      * {@inheritDoc}
      *
      * @return {@inheritDoc}
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 0f64309..543439e 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
@@ -156,13 +156,10 @@
 
     /**
      * Returns the order of the given axis direction.
-     * The returned value may be negative.
      */
     private static int order(final AxisDirection dir) {
         final Integer p = ORDER.get(dir);
-        if (p != null) return p;
-        if (AxisDirections.isLegacyOther(dir)) return -1;
-        return dir.ordinal() << SHIFT;
+        return (p != null) ? p : (dir.ordinal() << SHIFT);
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/SubTypes.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/SubTypes.java
index 47f7d4b..f18e654 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/SubTypes.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/SubTypes.java
@@ -30,6 +30,9 @@
 import org.opengis.referencing.cs.VerticalCS;
 import org.apache.sis.referencing.privy.AxisDirections;
 
+// Specific to the main and geoapi-3.1 branches:
+import org.opengis.referencing.cs.UserDefinedCS;
+
 
 /**
  * Implementation of {@link AbstractCS} methods that require knowledge about subclasses.
@@ -55,6 +58,7 @@
      *
      * @see AbstractCS#castOrCopy(CoordinateSystem)
      */
+    @SuppressWarnings("deprecation")
     static AbstractCS castOrCopy(final CoordinateSystem object) {
         if (object instanceof AffineCS) {
             return DefaultAffineCS.castOrCopy((AffineCS) object);
@@ -80,6 +84,9 @@
         if (object instanceof TimeCS) {
             return DefaultTimeCS.castOrCopy((TimeCS) object);
         }
+        if (object instanceof UserDefinedCS) {
+            return DefaultUserDefinedCS.castOrCopy((UserDefinedCS) object);
+        }
         /*
          * Intentionally check for AbstractCS after the interfaces because user may have defined his own
          * subclass implementing the interface. If we were checking for AbstractCS before the interfaces,
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 ce18679..ee28767 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
@@ -44,17 +44,9 @@
 import static org.apache.sis.util.Utilities.deepEquals;
 import static org.apache.sis.util.collection.Containers.property;
 
-// Specific to the main and geoapi-4.0 branches:
-import org.apache.sis.util.privy.TemporalDate;
-
 // Specific to the geoapi-3.1 and geoapi-4.0 branches:
 import org.opengis.metadata.Identifier;
 
-// Specific to the geoapi-4.0 branch:
-import org.opengis.annotation.UML;
-import static org.opengis.annotation.Obligation.*;
-import static org.opengis.annotation.Specification.*;
-
 
 /**
  * Specifies the relationship of a {@linkplain org.apache.sis.referencing.cs.AbstractCS Coordinate System} to the earth.
@@ -171,15 +163,16 @@
      *
      * @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);
         if (anchorDefinition == null) {
-            anchorDefinition = Types.toInternationalString(properties, "anchorPoint");      // Legacy name.
+            anchorDefinition = Types.toInternationalString(properties, ANCHOR_POINT_KEY);
         }
         anchorEpoch = property(properties, ANCHOR_EPOCH_KEY, Temporal.class);
         if (anchorEpoch == null) {
-            Date date = property(properties, "realizationEpoch", Date.class);               // Legacy name.
+            Date date = property(properties, REALIZATION_EPOCH_KEY, Date.class);
             if (date != null) {
                 anchorEpoch = date.toInstant();
             }
@@ -210,8 +203,9 @@
      *   <li>Otherwise if the given object is an instance of
      *       {@link org.opengis.referencing.datum.GeodeticDatum},
      *       {@link org.opengis.referencing.datum.VerticalDatum},
-     *       {@link org.opengis.referencing.datum.TemporalDatum} or
-     *       {@link org.opengis.referencing.datum.EngineeringDatum},
+     *       {@link org.opengis.referencing.datum.TemporalDatum},
+     *       {@link org.opengis.referencing.datum.EngineeringDatum} or
+     *       {@link org.opengis.referencing.datum.ImageDatum},
      *       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>
@@ -268,6 +262,20 @@
     }
 
     /**
+     * Returns a description of the point(s) used to anchor the datum to the Earth.
+     *
+     * @deprecated Renamed {@link #getAnchorDefinition()} as of ISO 19111:2019.
+     *
+     * @return a description of the point(s) used to anchor the datum to the Earth.
+     */
+    @Override
+    @Deprecated(since = "1.5")
+    @XmlElement(name = "anchorDefinition")
+    public InternationalString getAnchorPoint() {
+        return anchorDefinition;
+    }
+
+    /**
      * Returns the epoch at which a static datum matches a dynamic datum from which it has been derived.
      * This time may be precise or merely a year (e.g. 1983 for NAD83).
      *
@@ -285,6 +293,21 @@
     }
 
     /**
+     * The time after which this datum definition is valid.
+     *
+     * @return the time after which this datum definition is valid, or {@code null} if none.
+     *
+     * @deprecated Since ISO 19111:2019, replaced by {@link #getAnchorEpoch()}.
+     */
+    @Override
+    @Deprecated(since = "1.5")
+    @XmlSchemaType(name = "date")
+    @XmlElement(name = "realizationEpoch")
+    public Date getRealizationEpoch() {
+        return Datum.super.getRealizationEpoch();
+    }
+
+    /**
      * Returns {@code true} if either the {@linkplain #getName() primary name} or at least
      * one {@linkplain #getAlias() alias} matches the given string according heuristic rules.
      * This method performs the comparison documented in the
@@ -449,15 +472,6 @@
     }
 
     /**
-     * Returns a description of the point(s) used to anchor the datum to the Earth.
-     */
-    @XmlElement(name = "anchorDefinition")
-    @UML(identifier="anchorPoint", obligation=OPTIONAL, specification=ISO_19111, version=2003)
-    private InternationalString getAnchorPoint() {
-        return getAnchorDefinition().orElse(null);
-    }
-
-    /**
      * Invoked by JAXB only at unmarshalling time.
      *
      * @see #getAnchorPoint()
@@ -471,16 +485,6 @@
     }
 
     /**
-     * The time after which this datum definition is valid.
-     */
-    @XmlSchemaType(name = "date")
-    @XmlElement(name = "realizationEpoch")
-    @UML(identifier="realizationEpoch", obligation=OPTIONAL, specification=ISO_19111, version=2007)
-    private Date getRealizationEpoch() {
-        return getAnchorEpoch().map(TemporalDate::toDate).orElse(null);
-    }
-
-    /**
      * Invoked by JAXB only at unmarshalling time.
      *
      * @see #getRealizationEpoch()
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 4a41244..af6adc7 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
@@ -237,7 +237,7 @@
      * <p><b>Maintenance note:</b>
      * if the above policy regarding prime meridians is modified, then some {@code createOperationStep(…)} method
      * implementations in {@link org.apache.sis.referencing.operation.CoordinateOperationFinder} may need to be
-     * revisited. See especially the methods creating a transformation between a pair of geocentric CRS or
+     * revisited. See especially the methods creating a transformation between a pair of {@code GeocentricCRS} or
      * between a pair of {@code GeographicCRS} (tip: search for {@code DefaultGeodeticDatum}).</p>
      *
      * @param  pm  the prime meridian of the enclosing {@code GeodeticDatum}.
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 0838376..958903d 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
@@ -21,17 +21,18 @@
 import jakarta.xml.bind.annotation.XmlType;
 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.CollapsedStringAdapter;
 import org.opengis.util.GenericName;
 import org.opengis.util.InternationalString;
+import org.opengis.referencing.datum.PixelInCell;
 import org.apache.sis.referencing.privy.WKTKeywords;
 import org.apache.sis.metadata.privy.ImplementationHelper;
 import org.apache.sis.io.wkt.Formatter;
 import org.apache.sis.io.wkt.Convention;
-import org.apache.sis.io.wkt.ElementKind;
 import org.apache.sis.util.ComparisonMode;
 
+// 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;
 
@@ -60,7 +61,7 @@
 @Deprecated(since="1.5", forRemoval=true)   // Actually to be moved to an internal package for GML and WKT purposes.
 @XmlType(name = "ImageDatumType")
 @XmlRootElement(name = "ImageDatum")
-public final class DefaultImageDatum extends AbstractDatum {
+public final class DefaultImageDatum extends AbstractDatum implements ImageDatum {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -74,7 +75,7 @@
      *
      * @see #getPixelInCell()
      */
-    private String pixelInCell;
+    private PixelInCell pixelInCell;
 
     /**
      * Creates an image datum from the given properties. The properties map is given
@@ -124,19 +125,66 @@
      *
      * @see org.apache.sis.referencing.factory.GeodeticObjectFactory#createImageDatum(Map, PixelInCell)
      */
-    public DefaultImageDatum(final Map<String,?> properties, final String pixelInCell) {
+    public DefaultImageDatum(final Map<String,?> properties, final PixelInCell pixelInCell) {
         super(properties);
         this.pixelInCell = Objects.requireNonNull(pixelInCell);
     }
 
     /**
+     * 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.
+     *
+     * <p>This constructor performs a shallow copy, i.e. the properties are not cloned.</p>
+     *
+     * @param  datum  the datum to copy.
+     *
+     * @see #castOrCopy(ImageDatum)
+     */
+    protected DefaultImageDatum(final ImageDatum datum) {
+        super(datum);
+        pixelInCell = datum.getPixelInCell();
+    }
+
+    /**
+     * 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 DefaultImageDatum castOrCopy(final ImageDatum object) {
+        return (object == null) || (object instanceof DefaultImageDatum)
+                ? (DefaultImageDatum) object : new DefaultImageDatum(object);
+    }
+
+    /**
+     * Returns the GeoAPI interface implemented by this class.
+     * The SIS implementation returns {@code ImageDatum.class}.
+     *
+     * <h4>Note for implementers</h4>
+     * Subclasses usually do not need to override this method since GeoAPI does not define {@code ImageDatum}
+     * sub-interface. Overriding possibility is left mostly for implementers who wish to extend GeoAPI with
+     * their own set of interfaces.
+     *
+     * @return {@code ImageDatum.class} or a user-defined sub-interface.
+     */
+    @Override
+    public Class<? extends ImageDatum> getInterface() {
+        return ImageDatum.class;
+    }
+
+    /**
      * Specification of the way the image grid is associated with the image data attributes.
      *
      * @return the way image grid is associated with image data attributes.
      */
+    @Override
     @XmlElement(required = true)
-    @XmlJavaTypeAdapter(CollapsedStringAdapter.class)
-    public String getPixelInCell() {
+    public PixelInCell getPixelInCell() {
         return pixelInCell;
     }
 
@@ -154,8 +202,17 @@
         if (object == this) {
             return true;        // Slight optimization.
         }
-        return (object instanceof DefaultImageDatum) &&
-                Objects.equals(pixelInCell, ((DefaultImageDatum) object).pixelInCell);
+        if (!super.equals(object, mode)) {
+            return false;
+        }
+        switch (mode) {
+            case STRICT: {
+                return Objects.equals(pixelInCell, ((DefaultImageDatum) object).pixelInCell);
+            }
+            default: {
+                return Objects.equals(getPixelInCell(), ((ImageDatum) object).getPixelInCell());
+            }
+        }
     }
 
     /**
@@ -185,7 +242,7 @@
         super.formatTo(formatter);
         final Convention convention = formatter.getConvention();
         if (convention == Convention.INTERNAL) {
-            formatter.append(getPixelInCell(), ElementKind.CODE_LIST);    // This is an extension compared to ISO 19162.
+            formatter.append(getPixelInCell());         // This is an extension compared to ISO 19162.
         } else if (convention.majorVersion() == 1) {
             formatter.setInvalidWKT(this, null);
         }
@@ -221,7 +278,7 @@
      *
      * @see #getPixelInCell()
      */
-    private void setPixelInCell(final String value) {
+    private void setPixelInCell(final PixelInCell value) {
         if (pixelInCell == null) {
             pixelInCell = value;
         } else {
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 fcd095f..f60c234 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
@@ -40,12 +40,12 @@
 import org.apache.sis.measure.Units;
 import static org.apache.sis.util.ArgumentChecks.ensureFinite;
 
+// 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 geoapi-4.0 branch:
-import org.opengis.referencing.crs.DerivedCRS;
-
 
 /**
  * Defines the origin from which longitude values are determined.
@@ -328,8 +328,9 @@
      *
      * @see org.apache.sis.referencing.crs.AbstractCRS#isBaseCRS(Formatter)
      */
+    @SuppressWarnings("deprecation")
     private static boolean isElementOfBaseCRS(final Formatter formatter) {
-        return formatter.getEnclosingElement(2) instanceof DerivedCRS;
+        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 51ebb99..cc44c7e 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,6 +34,9 @@
 import org.apache.sis.io.wkt.Formatter;
 import org.apache.sis.io.wkt.FormattableObject;
 
+// 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;
 
@@ -166,7 +169,7 @@
      */
     protected DefaultTemporalDatum(final TemporalDatum datum) {
         super(datum);
-        origin = datum.getOrigin();
+        origin = TemporalDate.toTemporal(datum.getOrigin());
     }
 
     /**
@@ -206,8 +209,8 @@
      * @return the date and time origin of this temporal datum.
      */
     @Override
-    public Temporal getOrigin() {
-        return origin;
+    public Date getOrigin() {
+        return TemporalDate.toDate(origin);
     }
 
     /**
@@ -262,7 +265,7 @@
     @Override
     protected String formatTo(final Formatter formatter) {
         super.formatTo(formatter);
-        formatter.append(new Origin(getOrigin()));
+        formatter.append(new Origin(TemporalDate.toTemporal(getOrigin())));
         if (formatter.getConvention().majorVersion() == 1) {
             formatter.setInvalidWKT(this, null);
         }
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 6bbcdd4..7c3ccf4 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
@@ -32,15 +32,14 @@
 import org.apache.sis.referencing.internal.VerticalDatumTypes;
 import org.apache.sis.metadata.privy.ImplementationHelper;
 
+// 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 geoapi-4.0 branch:
-import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
-import jakarta.xml.bind.annotation.adapters.CollapsedStringAdapter;
-
 
 /**
  * Identifies a particular reference level surface used as a zero-height surface.
@@ -100,6 +99,14 @@
     private RealizationMethod method;
 
     /**
+     * The type of this vertical datum.
+     *
+     * @see #getVerticalDatumType()
+     */
+    @SuppressWarnings("deprecation")
+    private VerticalDatumType type;
+
+    /**
      * Creates a vertical datum from the given properties. The properties map is given
      * unchanged to the {@linkplain AbstractDatum#AbstractDatum(Map) super-class constructor}.
      * The following table is a reminder of main (not all) properties:
@@ -150,6 +157,22 @@
     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}.
+     */
+    @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);
     }
 
     /**
@@ -163,9 +186,11 @@
      *
      * @see #castOrCopy(VerticalDatum)
      */
+    @SuppressWarnings("deprecation")
     protected DefaultVerticalDatum(final VerticalDatum datum) {
         super(datum);
         method = datum.getRealizationMethod().orElse(null);
+        type = datum.getVerticalDatumType();
     }
 
     /**
@@ -212,6 +237,25 @@
     }
 
     /**
+     * Returns the type of this vertical datum.
+     *
+     * <h4>Historical note:</h4>
+     * This property was defined in the ISO 19111 specification published in 2003,
+     * but removed from the revision published 2007.
+     * This property provides an information similar to the {@linkplain #getAnchorPoint() anchor definition},
+     * 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;
+    }
+
+    /**
      * Compares this vertical datum with the specified object for equality.
      *
      * @param  object  the object to compare to {@code this}.
@@ -221,6 +265,7 @@
      * @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.
@@ -231,11 +276,12 @@
         switch (mode) {
             case STRICT: {
                 final var other = (DefaultVerticalDatum) object;
-                return Objects.equals(method, other.method);
+                return Objects.equals(method, other.method) && Objects.equals(type, other.type);
             }
             case BY_CONTRACT: {
                 final var other = (VerticalDatum) object;
-                return Objects.equals(getRealizationMethod(), other.getRealizationMethod());
+                return Objects.equals(getRealizationMethod(), other.getRealizationMethod()) &&
+                       Objects.equals(getVerticalDatumType(), other.getVerticalDatumType());
             }
             default: {
                 /*
@@ -277,7 +323,7 @@
     protected String formatTo(final Formatter formatter) {
         super.formatTo(formatter);
         if (formatter.getConvention().majorVersion() == 1) {
-            formatter.append(VerticalDatumTypes.toLegacy(method));
+            formatter.append(VerticalDatumTypes.toLegacy(getVerticalDatumType()));
             return WKTKeywords.Vert_Datum;
         }
         return formatter.shortOrLong(WKTKeywords.VDatum, WKTKeywords.VerticalDatum);
@@ -313,23 +359,22 @@
      *
      * @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")
-    @XmlJavaTypeAdapter(CollapsedStringAdapter.class)
-    private String getTypeElement() {
-        if (Context.isGMLVersion(Context.current(), LegacyNamespaces.VERSION_3_2)) {
-            return null;
-        }
-        return VerticalDatumTypes.toName(method);
+    private VerticalDatumType getTypeElement() {
+        return Context.isGMLVersion(Context.current(), LegacyNamespaces.VERSION_3_2) ? null : getVerticalDatumType();
     }
 
     /**
      * Invoked by JAXB only. The vertical datum type is set only if it has not already been specified.
      */
-    private void setTypeElement(final String type) {
-        if (method == null) {
-            method = VerticalDatumTypes.fromName(type);
+    @SuppressWarnings("deprecation")
+    private void setTypeElement(final VerticalDatumType value) {
+        if (type == null) {
+            type = value;
+            method = VerticalDatumTypes.toMethod(value);
         } else {
-            ImplementationHelper.propertyAlreadySet(DefaultVerticalDatum.class, "setType", "verticalDatumType");
+            ImplementationHelper.propertyAlreadySet(DefaultVerticalDatum.class, "setTypeElement", "verticalDatumType");
         }
     }
 }
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/SubTypes.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/SubTypes.java
index dfb9e3b..74cad89 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/SubTypes.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/SubTypes.java
@@ -22,6 +22,9 @@
 import org.opengis.referencing.datum.TemporalDatum;
 import org.opengis.referencing.datum.EngineeringDatum;
 
+// Specific to the main and geoapi-3.1 branches:
+import org.opengis.referencing.datum.ImageDatum;
+
 
 /**
  * Implementation of {@link AbstractDatum} methods that require knowledge about subclasses.
@@ -47,6 +50,7 @@
      *
      * @see AbstractDatum#castOrCopy(Datum)
      */
+    @SuppressWarnings("deprecation")
     static AbstractDatum castOrCopy(final Datum object) {
         if (object instanceof GeodeticDatum) {
             return DefaultGeodeticDatum.castOrCopy((GeodeticDatum) object);
@@ -60,6 +64,9 @@
         if (object instanceof EngineeringDatum) {
             return DefaultEngineeringDatum.castOrCopy((EngineeringDatum) object);
         }
+        if (object instanceof ImageDatum) {
+            return DefaultImageDatum.castOrCopy((ImageDatum) object);
+        }
         /*
          * Intentionally check for AbstractDatum after the interfaces because user may have defined his own
          * subclass implementing the interface. If we were checking for AbstractDatum before the interfaces,
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/package-info.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/package-info.java
index 4f853f8..9a1b93d 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/package-info.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/package-info.java
@@ -69,6 +69,8 @@
     @XmlJavaTypeAdapter(EX_Extent.class),
     @XmlJavaTypeAdapter(CD_Ellipsoid.class),
     @XmlJavaTypeAdapter(CD_PrimeMeridian.class),
+    @XmlJavaTypeAdapter(CD_VerticalDatumType.class),
+    @XmlJavaTypeAdapter(CD_PixelInCell.class),
     @XmlJavaTypeAdapter(StringAdapter.class),
     @XmlJavaTypeAdapter(InternationalStringConverter.class),
     @XmlJavaTypeAdapter(DateAdapter.class),
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 6e13333..dd8756e 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
@@ -190,16 +190,11 @@
     /**
      * 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 create(GeodeticAuthorityFactory factory, String code) throws FactoryException {
-                return factory.createObject(code);
-            }
             @Override IdentifiedObject createFromAPI(AuthorityFactory factory, String code) throws FactoryException {
-                if (factory instanceof GeodeticAuthorityFactory) {
-                    return ((GeodeticAuthorityFactory) factory).createObject(code);
-                }
-                throw new FactoryException(Errors.format(Errors.Keys.UnsupportedOperation_1, "createObject"));
+                return factory.createObject(code);
             }
     };
 
@@ -223,6 +218,17 @@
             }
     };
 
+    @SuppressWarnings("deprecation")
+    static final AuthorityFactoryProxy<ImageDatum> IMAGE_DATUM =
+        new AuthorityFactoryProxy<ImageDatum>(ImageDatum.class, AuthorityFactoryIdentifier.DATUM) {
+            @Override ImageDatum create(GeodeticAuthorityFactory factory, String code) throws FactoryException {
+                return factory.createImageDatum(code);
+            }
+            @Override ImageDatum createFromAPI(AuthorityFactory factory, String code) throws FactoryException {
+                return datumFactory(factory).createImageDatum(code);
+            }
+    };
+
     static final AuthorityFactoryProxy<ParametricDatum> PARAMETRIC_DATUM =
         new AuthorityFactoryProxy<ParametricDatum>(ParametricDatum.class, AuthorityFactoryIdentifier.DATUM) {
             @Override ParametricDatum create(GeodeticAuthorityFactory factory, String code) throws FactoryException {
@@ -454,6 +460,17 @@
             }
     };
 
+    @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 {
+                return factory.createGeocentricCRS(code);
+            }
+            @Override GeocentricCRS createFromAPI(AuthorityFactory factory, String code) throws FactoryException {
+                return crsFactory(factory).createGeocentricCRS(code);
+            }
+    };
+
     static final AuthorityFactoryProxy<GeodeticCRS> GEODETIC_CRS =
         new AuthorityFactoryProxy<GeodeticCRS>(GeodeticCRS.class, AuthorityFactoryIdentifier.CRS) {
             @Override GeodeticCRS create(GeodeticAuthorityFactory factory, String code) throws FactoryException {
@@ -464,6 +481,17 @@
             }
     };
 
+    @SuppressWarnings("deprecation")
+    static final AuthorityFactoryProxy<ImageCRS> IMAGE_CRS =
+        new AuthorityFactoryProxy<ImageCRS>(ImageCRS.class, AuthorityFactoryIdentifier.CRS) {
+            @Override ImageCRS create(GeodeticAuthorityFactory factory, String code) throws FactoryException {
+                return factory.createImageCRS(code);
+            }
+            @Override ImageCRS createFromAPI(AuthorityFactory factory, String code) throws FactoryException {
+                return crsFactory(factory).createImageCRS(code);
+            }
+    };
+
     static final AuthorityFactoryProxy<ProjectedCRS> PROJECTED_CRS =
         new AuthorityFactoryProxy<ProjectedCRS>(ProjectedCRS.class, AuthorityFactoryIdentifier.CRS) {
             @Override ProjectedCRS create(GeodeticAuthorityFactory factory, String code) throws FactoryException {
@@ -540,12 +568,15 @@
      * 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 DerivedCRS.
+        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).
         ENGINEERING_CRS,
         DERIVED_CRS,        // DerivedCRS can be also Vertical, Temporal or Engineering CRS. Give precedence to those.
         COMPOUND_CRS,
@@ -553,6 +584,7 @@
         GEODETIC_DATUM,
         VERTICAL_DATUM,
         TEMPORAL_DATUM,
+        IMAGE_DATUM,        // Can be seen as a special kind of EngineeringDatum (even if not shown in hierarchy).
         ENGINEERING_DATUM,
         DATUM,
         ELLIPSOID,
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 4f9cb21..089d027 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
@@ -53,9 +53,6 @@
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.measure.Units;
 
-// Specific to the geoapi-4.0 branch:
-import org.opengis.referencing.crs.GeodeticCRS;
-
 
 /**
  * Creates coordinate reference systems in the "{@code OGC}", "{@code CRS}" or {@code "AUTO(2)"} namespaces.
@@ -459,6 +456,7 @@
      * @throws FactoryException if the object creation failed.
      */
     @Override
+    @SuppressWarnings("removal")
     public IdentifiedObject createObject(final String code) throws FactoryException {
         return createCoordinateReferenceSystem(code);
     }
@@ -594,7 +592,7 @@
          * because the WMS specification does not said that we should.
          */
         final CommonCRS datum = CommonCRS.WGS84;
-        final GeodeticCRS baseCRS;                  // To be set, directly or indirectly, to WGS84.geographic().
+        final GeographicCRS baseCRS;                // To be set, directly or indirectly, to WGS84.geographic().
         final ProjectedCRS crs;                     // Temporary UTM projection, for extracting other properties.
         CartesianCS cs;                             // Coordinate system with (E,N) axes in metres.
         try {
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 6f4fecb..dbf400f 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,6 +873,7 @@
      * @throws FactoryException if the object creation failed.
      */
     @Override
+    @SuppressWarnings("removal")
     public IdentifiedObject createObject(final String code) throws FactoryException {
         return create(AuthorityFactoryProxy.OBJECT, code);
     }
@@ -949,6 +950,24 @@
     }
 
     /**
+     * Returns a 3-dimensional coordinate reference system with the origin at the approximate centre of mass of the earth.
+     *
+     * @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);
+        }
+        return create(AuthorityFactoryProxy.GEOCENTRIC_CRS, code);
+    }
+
+    /**
      * Returns a 2-dimensional coordinate reference system used to approximate the shape of the earth on a planar surface.
      * The default implementation performs the following steps:
      * <ul>
@@ -1112,6 +1131,33 @@
     }
 
     /**
+     * Returns a 2-dimensional engineering coordinate reference system applied to locations in images.
+     * 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 createImageCRS(String)}
+     *       method, invoke that method and cache the result for future use.</li>
+     *   <li>Otherwise delegate to the {@link GeodeticAuthorityFactory#createImageCRS(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.
+     *
+     * @deprecated The {@code ImageCRS} class has been removed in ISO 19111:2019.
+     *             It is replaced by {@code EngineeringCRS}.
+     */
+    @Override
+    @Deprecated(since = "1.5")
+    public ImageCRS createImageCRS(final String code) throws FactoryException {
+        if (isDefault(ImageCRS.class)) {
+            return super.createImageCRS(code);
+        }
+        return create(AuthorityFactoryProxy.IMAGE_CRS, code);
+    }
+
+    /**
      * Returns an arbitrary datum from a code. The returned object will typically be an
      * The default implementation performs the following steps:
      * <ul>
@@ -1252,6 +1298,33 @@
     }
 
     /**
+     * Returns a datum defining the origin of an image coordinate reference 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 createImageDatum(String)}
+     *       method, invoke that method and cache the result for future use.</li>
+     *   <li>Otherwise delegate to the {@link GeodeticAuthorityFactory#createImageDatum(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.
+     *
+     * @deprecated The {@code ImageDatum} class has been removed in ISO 19111:2019.
+     *             It is replaced by {@code EngineeringDatum}.
+     */
+    @Override
+    @Deprecated(since = "1.5")
+    public ImageDatum createImageDatum(final String code) throws FactoryException {
+        if (isDefault(ImageDatum.class)) {
+            return super.createImageDatum(code);
+        }
+        return create(AuthorityFactoryProxy.IMAGE_DATUM, code);
+    }
+
+    /**
      * Returns a geometric figure that can be used to describe the approximate shape of the earth.
      * 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 e2d7464..450c464 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
@@ -211,6 +211,8 @@
      *
      * @see org.apache.sis.referencing.AbstractIdentifiedObject
      */
+    @Override
+    @SuppressWarnings("removal")
     public abstract IdentifiedObject createObject(String code) throws NoSuchAuthorityCodeException, FactoryException;
 
     /**
@@ -340,6 +342,22 @@
     }
 
     /**
+     * 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);
+    }
+
+    /**
      * Creates a 2-dimensional coordinate reference system used to approximate the shape of the earth on a planar surface.
      * It is done in such a way that the distortion that is inherent to the approximation is carefully controlled and known.
      * Distortion correction is commonly applied to calculated bearings and distances to produce values
@@ -523,6 +541,30 @@
     }
 
     /**
+     * Creates a 2-dimensional engineering coordinate reference system applied to locations in images.
+     * Image coordinate reference systems are treated as a separate sub-type because a separate
+     * user community exists for images with its own terms of reference.
+     *
+     * <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.
+     *
+     * @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 for some other reason.
+     *
+     * @see org.apache.sis.referencing.crs.DefaultImageCRS
+     *
+     * @deprecated The {@code ImageCRS} class has been removed in ISO 19111:2019.
+     *             It is replaced by {@code EngineeringCRS}.
+     */
+    @Deprecated(since = "1.5")
+    public ImageCRS createImageCRS(final String code) throws NoSuchAuthorityCodeException, FactoryException {
+        return cast(ImageCRS.class, createCoordinateReferenceSystem(code), code);
+    }
+
+    /**
      * Creates an arbitrary datum from a code. The returned object will typically be an
      * instance of {@link GeodeticDatum}, {@link VerticalDatum} or {@link TemporalDatum}.
      * If the datum is known at compile time, it is recommended to invoke the most precise method instead of this one.
@@ -697,6 +739,30 @@
     }
 
     /**
+     * Creates a datum defining the origin of an image coordinate reference system.
+     * An image datum is used in a local context only.
+     * For an image datum, the anchor point is usually either the centre of the image or the corner of the image.
+     *
+     * <h4>Default implementation</h4>
+     * The default implementation delegates to {@link #createDatum(String)} and casts the result.
+     * If the result cannot be casted, then a {@link NoSuchAuthorityCodeException} is thrown.
+     *
+     * @param  code  value allocated by authority.
+     * @return the datum for the given code.
+     * @throws NoSuchAuthorityCodeException if the specified {@code code} was not found.
+     * @throws FactoryException if the object creation failed for some other reason.
+     *
+     * @see org.apache.sis.referencing.datum.DefaultImageDatum
+     *
+     * @deprecated The {@code ImageDatum} class has been removed in ISO 19111:2019.
+     *             It is replaced by {@code EngineeringDatum}.
+     */
+    @Deprecated(since = "1.5")
+    public ImageDatum createImageDatum(final String code) throws NoSuchAuthorityCodeException, FactoryException {
+        return cast(ImageDatum.class, createDatum(code), code);
+    }
+
+    /**
      * Creates a geometric figure that can be used to describe the approximate shape of the earth.
      * In mathematical terms, it is a surface formed by the rotation of an ellipse about its minor axis.
      *
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 4345b18..20e1cc7 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
@@ -365,6 +365,31 @@
     }
 
     /**
+     * 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  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.
+     */
+    @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
+    {
+        final DefaultGeocentricCRS crs;
+        try {
+            crs = new DefaultGeocentricCRS(complete(properties), datum, cs);
+        } catch (IllegalArgumentException exception) {
+            throw new InvalidGeodeticParameterException(exception);
+        }
+        return unique("createGeocentricCRS", crs);
+    }
+
+    /**
      * Creates a three-dimensional Cartesian coordinate system from the given set of axis.
      * This coordinate system can be used with geocentric, engineering and derived CRS.
      *
@@ -450,6 +475,31 @@
     }
 
     /**
+     * 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
+    {
+        final DefaultGeocentricCRS crs;
+        try {
+            crs = new DefaultGeocentricCRS(complete(properties), datum, cs);
+        } catch (IllegalArgumentException exception) {
+            throw new InvalidGeodeticParameterException(exception);
+        }
+        return unique("createGeocentricCRS", crs);
+    }
+
+    /**
      * Creates a spherical coordinate system from the given set of axis.
      * This coordinate system can be used with geocentric, engineering and derived CRS.
      *
@@ -797,7 +847,7 @@
      */
     @Override
     public ProjectedCRS createProjectedCRS(final Map<String,?> properties,
-            final GeodeticCRS baseCRS, final Conversion conversion,
+            final GeographicCRS baseCRS, final Conversion conversion,
             final CartesianCS derivedCS) throws FactoryException
     {
         final DefaultProjectedCRS crs;
@@ -965,6 +1015,33 @@
     }
 
     /**
+     * Creates a vertical datum from an enumerated type value.
+     * The default implementation creates a {@link DefaultVerticalDatum} instance.
+     *
+     * @param  properties  name and other properties to give to the new object.
+     * @param  type        the type of this vertical datum (often geoidal).
+     * @throws FactoryException if the object creation failed.
+     *
+     * @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
+    {
+        final DefaultVerticalDatum datum;
+        try {
+            datum = new DefaultVerticalDatum(complete(properties), type);
+        } catch (IllegalArgumentException exception) {
+            throw new InvalidGeodeticParameterException(exception);
+        }
+        return unique("createVerticalDatum", datum);
+    }
+
+    /**
      * Creates a vertical coordinate system.
      * This coordinate system can be used with vertical and derived CRS.
      *
@@ -1220,6 +1297,63 @@
     }
 
     /**
+     * Creates an image coordinate reference system.
+     * The default implementation creates a {@link DefaultImageCRS} instance.
+     *
+     * @param  properties  name and other properties to give to the new object.
+     * @param  datum       the image datum to use in created CRS.
+     * @param  cs          the Cartesian or oblique Cartesian coordinate system for the created CRS.
+     * @throws FactoryException if the object creation failed.
+     *
+     * @see DefaultImageCRS#DefaultImageCRS(Map, ImageDatum, AffineCS)
+     * @see GeodeticAuthorityFactory#createImageCRS(String)
+     *
+     * @deprecated The {@code ImageCRS} class has been removed in ISO 19111:2019.
+     *             It is replaced by {@code EngineeringCRS}.
+     */
+    @Override
+    @Deprecated(since = "1.5")
+    public ImageCRS createImageCRS(final Map<String,?> properties,
+            final ImageDatum datum, final AffineCS cs) throws FactoryException
+    {
+        final DefaultImageCRS crs;
+        try {
+            crs = new DefaultImageCRS(complete(properties), datum, cs);
+        } catch (IllegalArgumentException exception) {
+            throw new InvalidGeodeticParameterException(exception);
+        }
+        return unique("createImageCRS", crs);
+    }
+
+    /**
+     * Creates an image datum.
+     * The default implementation creates a {@link DefaultImageDatum} instance.
+     *
+     * @param  properties  Name and other properties to give to the new object.
+     * @param  pixelInCell Specification of the way the image grid is associated with the image data attributes.
+     * @throws FactoryException if the object creation failed.
+     *
+     * @see DefaultImageDatum#DefaultImageDatum(Map, PixelInCell)
+     * @see GeodeticAuthorityFactory#createImageDatum(String)
+     *
+     * @deprecated The {@code ImageDatum} class has been removed in ISO 19111:2019.
+     *             It is replaced by {@code EngineeringDatum}.
+     */
+    @Override
+    @Deprecated(since = "1.5")
+    public ImageDatum createImageDatum(final Map<String,?> properties,
+            final PixelInCell pixelInCell) throws FactoryException
+    {
+        final DefaultImageDatum datum;
+        try {
+            datum = new DefaultImageDatum(complete(properties), pixelInCell);
+        } catch (IllegalArgumentException exception) {
+            throw new InvalidGeodeticParameterException(exception);
+        }
+        return unique("createImageDatum", datum);
+    }
+
+    /**
      * Creates a two-dimensional affine coordinate system from the given pair of axis.
      * This coordinate system can be used with image and engineering CRS.
      *
@@ -1457,6 +1591,80 @@
     }
 
     /**
+     * Creates a two-dimensional user defined coordinate system from the given pair of axis.
+     * This coordinate system can be used with engineering CRS.
+     *
+     * <h4>Dependencies</h4>
+     * The components needed by this method can be created by the following methods:
+     * <ol>
+     *   <li>{@link #createCoordinateSystemAxis(Map, String, AxisDirection, Unit)}</li>
+     * </ol>
+     *
+     * The default implementation creates a {@link DefaultUserDefinedCS} instance.
+     *
+     * @param  properties  name and other properties to give to the new object.
+     * @param  axis0       the first  axis.
+     * @param  axis1       the second axis.
+     * @throws FactoryException if the object creation failed.
+     *
+     * @see DefaultUserDefinedCS#DefaultUserDefinedCS(Map, CoordinateSystemAxis, CoordinateSystemAxis)
+     *
+     * @deprecated The {@code UserDefinedCS} class has been removed from ISO 19111:2019.
+     */
+    @Override
+    @Deprecated(since = "1.5")
+    public UserDefinedCS createUserDefinedCS(final Map<String,?> properties,
+            final CoordinateSystemAxis axis0,
+            final CoordinateSystemAxis axis1) throws FactoryException
+    {
+        final DefaultUserDefinedCS cs;
+        try {
+            cs = new DefaultUserDefinedCS(complete(properties), axis0, axis1);
+        } catch (IllegalArgumentException exception) {
+            throw new InvalidGeodeticParameterException(exception);
+        }
+        return unique("createUserDefinedCS", cs);
+    }
+
+    /**
+     * Creates a three-dimensional user defined coordinate system from the given set of axis.
+     * This coordinate system can be used with engineering CRS.
+     *
+     * <h4>Dependencies</h4>
+     * The components needed by this method can be created by the following methods:
+     * <ol>
+     *   <li>{@link #createCoordinateSystemAxis(Map, String, AxisDirection, Unit)}</li>
+     * </ol>
+     *
+     * The default implementation creates a {@link DefaultUserDefinedCS} instance.
+     *
+     * @param  properties  name and other properties to give to the new object.
+     * @param  axis0       the first  axis.
+     * @param  axis1       the second axis.
+     * @param  axis2       the third  axis.
+     * @throws FactoryException if the object creation failed.
+     *
+     * @see DefaultUserDefinedCS#DefaultUserDefinedCS(Map, CoordinateSystemAxis, CoordinateSystemAxis, CoordinateSystemAxis)
+     *
+     * @deprecated The {@code UserDefinedCS} class has been removed from ISO 19111:2019.
+     */
+    @Override
+    @Deprecated(since = "1.5")
+    public UserDefinedCS createUserDefinedCS(final Map<String,?> properties,
+            final CoordinateSystemAxis axis0,
+            final CoordinateSystemAxis axis1,
+            final CoordinateSystemAxis axis2) throws FactoryException
+    {
+        final DefaultUserDefinedCS cs;
+        try {
+            cs = new DefaultUserDefinedCS(complete(properties), axis0, axis1, axis2);
+        } catch (IllegalArgumentException exception) {
+            throw new InvalidGeodeticParameterException(exception);
+        }
+        return unique("createUserDefinedCS", cs);
+    }
+
+    /**
      * Creates a coordinate system axis from an abbreviation and a unit.
      * Note that the axis name is constrained by ISO 19111 depending on the coordinate reference system type.
      * See the GeoAPI {@link CoordinateSystemAxis} javadoc for more information.
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 3582606..e68b77c 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,6 +901,7 @@
      * @throws FactoryException if the object creation failed.
      */
     @Override
+    @SuppressWarnings("removal")
     public IdentifiedObject createObject(final String code) throws FactoryException {
         return create(AuthorityFactoryProxy.OBJECT, code);
     }
@@ -965,6 +966,21 @@
     }
 
     /**
+     * Creates a 3-dimensional coordinate reference system with the origin at the approximate centre of mass of the earth.
+     *
+     * @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);
+    }
+
+    /**
      * Creates a 2-dimensional coordinate reference system used to approximate the shape of the earth on a planar surface.
      * The given code can use any of the following patterns, where <var>version</var> is optional:
      * <ul>
@@ -1079,6 +1095,29 @@
     }
 
     /**
+     * Creates a 2-dimensional engineering coordinate reference system applied to locations in images.
+     * The given code can use any of the following patterns, where <var>version</var> is optional:
+     * <ul>
+     *   <li><var>authority</var>{@code :}<var>code</var></li>
+     *   <li><var>authority</var>{@code :}<var>version</var>{@code :}<var>code</var></li>
+     *   <li><code>urn:ogc:def:<b>crs</b>:</code><var>authority</var>{@code :}<var>version</var>{@code :}<var>code</var></li>
+     *   <li><code>http://www.opengis.net/def/<b>crs</b>/</code><var>authority</var>{@code /}<var>version</var>{@code /}<var>code</var></li>
+     *   <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.
+     *
+     * @deprecated The {@code ImageCRS} class has been removed in ISO 19111:2019.
+     *             It is replaced by {@code EngineeringCRS}.
+     */
+    @Override
+    @Deprecated(since = "1.5")
+    public ImageCRS createImageCRS(final String code) throws FactoryException {
+        return create(AuthorityFactoryProxy.IMAGE_CRS, code);
+    }
+
+    /**
      * Creates an arbitrary datum from a code. The returned object will typically be an
      * The given code can use any of the following patterns, where <var>version</var> is optional:
      * <ul>
@@ -1169,6 +1208,28 @@
     }
 
     /**
+     * Creates a datum defining the origin of an image coordinate reference system.
+     * The given code can use any of the following patterns, where <var>version</var> is optional:
+     * <ul>
+     *   <li><var>authority</var>{@code :}<var>code</var></li>
+     *   <li><var>authority</var>{@code :}<var>version</var>{@code :}<var>code</var></li>
+     *   <li><code>urn:ogc:def:<b>datum</b>:</code><var>authority</var>{@code :}<var>version</var>{@code :}<var>code</var></li>
+     *   <li><code>http://www.opengis.net/def/<b>datum</b>/</code><var>authority</var>{@code /}<var>version</var>{@code /}<var>code</var></li>
+     * </ul>
+     *
+     * @return the datum for the given code.
+     * @throws FactoryException if the object creation failed.
+     *
+     * @deprecated The {@code ImageDatum} class has been removed in ISO 19111:2019.
+     *             It is replaced by {@code EngineeringDatum}.
+     */
+    @Override
+    @Deprecated(since = "1.5")
+    public ImageDatum createImageDatum(final String code) throws FactoryException {
+        return create(AuthorityFactoryProxy.IMAGE_DATUM, code);
+    }
+
+    /**
      * Creates a geometric figure that can be used to describe the approximate shape of the earth.
      * The given code can use any of the following patterns, where <var>version</var> is optional:
      * <ul>
@@ -1688,8 +1749,8 @@
         if (baseCRS != null && fromBase.getSourceCRS() == null && fromBase.getTargetCRS() == null) {
             final Map<String,?> properties = IdentifiedObjects.getProperties(fromBase, Datum.IDENTIFIERS_KEY);
             final CRSFactory factory = GeodeticObjectFactory.provider();
-            if (baseCRS instanceof GeodeticCRS && cs instanceof CartesianCS) {
-                return factory.createProjectedCRS(properties, (GeodeticCRS) baseCRS, fromBase, (CartesianCS) cs);
+            if (baseCRS instanceof GeographicCRS && cs instanceof CartesianCS) {
+                return factory.createProjectedCRS(properties, (GeographicCRS) baseCRS, fromBase, (CartesianCS) cs);
             } else {
                 return factory.createDerivedCRS(properties, baseCRS, fromBase, cs);
             }
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 c52f883..a67aad7 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
@@ -29,8 +29,8 @@
 import org.apache.sis.referencing.IdentifiedObjects;
 import org.apache.sis.referencing.factory.IdentifiedObjectSet;
 
-// Specific to the geoapi-4.0 branch:
-import org.opengis.referencing.crs.DerivedCRS;
+// Specific to the main and geoapi-3.1 branches:
+import org.opengis.referencing.crs.GeneralDerivedCRS;
 
 
 /**
@@ -112,6 +112,7 @@
      * 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) {
@@ -124,8 +125,8 @@
              */
             CoordinateReferenceSystem crs;
             crs = ((CRSAuthorityFactory) factory).createCoordinateReferenceSystem(String.valueOf(base));
-            if (crs instanceof DerivedCRS) {
-                return ((DerivedCRS) crs).getConversionFromBase();
+            if (crs instanceof GeneralDerivedCRS) {
+                return ((GeneralDerivedCRS) crs).getConversionFromBase();
             }
         }
         /*
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGCodeFinder.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGCodeFinder.java
index 45a6228..8a843da 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGCodeFinder.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGCodeFinder.java
@@ -52,8 +52,8 @@
 import org.apache.sis.referencing.factory.ConcurrentAuthorityFactory;
 import static org.apache.sis.metadata.privy.NameToIdentifier.Simplifier.ESRI_DATUM_PREFIX;
 
-// Specific to the geoapi-4.0 branch:
-import org.opengis.referencing.crs.DerivedCRS;
+// Specific to the main and geoapi-3.1 branches:
+import org.opengis.referencing.crs.GeneralDerivedCRS;
 
 
 /**
@@ -318,8 +318,8 @@
              *     ORDER BY COORD_REF_SYS_CODE
              */
             final Condition filter;
-            if (object instanceof DerivedCRS) {              // No need to use isInstance(Class, Object) from here.
-                filter = dependencies("SOURCE_GEOGCRS_CODE", SingleCRS.class, ((DerivedCRS) object).getBaseCRS(), true);
+            if (object instanceof GeneralDerivedCRS) {              // No need to use isInstance(Class, Object) from here.
+                filter = dependencies("SOURCE_GEOGCRS_CODE", CoordinateReferenceSystem.class, ((GeneralDerivedCRS) object).getBaseCRS(), true);
             } else if (object instanceof GeodeticCRS) {
                 filter = dependencies("DATUM_CODE", GeodeticDatum.class, ((GeodeticCRS) object).getDatum(), true);
             } else if (object instanceof VerticalCRS) {
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 7343d3d..d0ca972 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
@@ -1235,6 +1235,7 @@
      * @see #createCoordinateSystem(String)
      */
     @Override
+    @SuppressWarnings("removal")
     public synchronized IdentifiedObject createObject(final String code)
             throws NoSuchAuthorityCodeException, FactoryException
     {
@@ -1478,8 +1479,8 @@
                                 @SuppressWarnings("LocalVariableHidesMemberVariable")
                                 final Map<String,Object> properties = createProperties("Coordinate Reference System",
                                                                         name, epsg, area, scope, remarks, deprecated);
-                                if (baseCRS instanceof GeodeticCRS) {
-                                    crs = crsFactory.createProjectedCRS(properties, (GeodeticCRS) baseCRS, op, cs);
+                                if (baseCRS instanceof GeographicCRS) {
+                                    crs = crsFactory.createProjectedCRS(properties, (GeographicCRS) baseCRS, op, cs);
                                 } else {
                                     crs = crsFactory.createDerivedCRS(properties, baseCRS, op, cs);
                                 }
@@ -1707,7 +1708,7 @@
                         break;
                     }
                     case "vertical": {
-                        datum = datumFactory.createVerticalDatum(properties, null);
+                        datum = datumFactory.createVerticalDatum(properties, (RealizationMethod) null);
                         break;
                     }
                     /*
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 1223b01..2e5cbb0 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,9 +26,6 @@
 import org.apache.sis.referencing.privy.WKTKeywords;
 import org.apache.sis.util.CharSequences;
 
-// Specific to the geoapi-4.0 branch:
-import org.apache.sis.referencing.crs.DefaultGeocentricCRS;
-
 
 /**
  * Information about a specific table. The MS-Access dialect of SQL is assumed;
@@ -64,20 +61,15 @@
      * {@link EPSGDataAccess#createUnit(String)}
      *
      * The order is significant: it is the key for a {@code switch} statement.
-     *
-     * <h4>Ambiguity</h4>
-     * As of ISO 19111:2019, we have no standard way to identify the geocentric case from a {@link Class} argument
-     * because the standard does not provide the {@code GeocentricCRS} interface. This implementation fallbacks on
-     * the SIS-specific geocentric CRS class, with a {@link #where(IdentifiedObject, StringBuilder)} method which
-     * will substitute implementation-neutral objects by the Apache SIS class.
      */
+    @SuppressWarnings("deprecation")
     static final TableInfo[] EPSG = {
         CRS = new TableInfo(CoordinateReferenceSystem.class,
                 "[Coordinate Reference System]",
                 "COORD_REF_SYS_CODE",
                 "COORD_REF_SYS_NAME",
                 "COORD_REF_SYS_KIND",
-                new Class<?>[] { ProjectedCRS.class,   GeographicCRS.class,   DefaultGeocentricCRS.class,
+                new Class<?>[] { ProjectedCRS.class,   GeographicCRS.class,   GeocentricCRS.class,
                                  VerticalCRS.class,    CompoundCRS.class,     EngineeringCRS.class,
                                  DerivedCRS.class,     TemporalCRS.class,     ParametricCRS.class},     // See comment below
                 new String[]   {"projected",          "geographic",          "geocentric",
@@ -266,6 +258,7 @@
      * @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) {
@@ -273,7 +266,7 @@
             if (cs instanceof EllipsoidalCS) {
                 userType = GeographicCRS.class;
             } else if (cs instanceof CartesianCS || cs instanceof SphericalCS) {
-                userType = DefaultGeocentricCRS.class;
+                userType = GeocentricCRS.class;
             }
         }
         where(userType, buffer);
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/DeprecatedCode.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/DeprecatedCode.java
index 710980a..54cb662 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/DeprecatedCode.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/DeprecatedCode.java
@@ -21,9 +21,6 @@
 import org.apache.sis.referencing.ImmutableIdentifier;
 import org.apache.sis.util.Deprecable;
 
-// Specific to the geoapi-4.0 branch:
-import java.util.Optional;
-
 
 /**
  * An identifier which should not be used anymore.
@@ -80,11 +77,11 @@
      *
      * <div class="note"><b>Example:</b> "superseded by code XYZ".</div>
      *
-     * @return information about the replacement for this identifier.
+     * @return information about the replacement for this identifier, or {@code null} if none.
      */
     @Override
-    public Optional<InternationalString> getRemarks() {
-        return Optional.ofNullable(super.getDescription());
+    public InternationalString getRemarks() {
+        return super.getDescription();
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/DeprecatedName.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/DeprecatedName.java
index 0807a35..d4b25f6 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/DeprecatedName.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/DeprecatedName.java
@@ -21,9 +21,6 @@
 import org.apache.sis.referencing.NamedIdentifier;
 import org.apache.sis.util.Deprecable;
 
-// Specific to the geoapi-4.0 branch:
-import java.util.Optional;
-
 
 /**
  * A deprecated name.
@@ -72,11 +69,11 @@
      *
      * <div class="note"><b>Example:</b> "superseded by code XYZ".</div>
      *
-     * @return information about the replacement for this name.
+     * @return information about the replacement for this name, or {@code null} if none.
      */
     @Override
-    public Optional<InternationalString> getRemarks() {
-        return Optional.ofNullable(super.getDescription());
+    public InternationalString getRemarks() {
+        return super.getDescription();
     }
 
     /**
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 d95a07f..84ab46c 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
@@ -68,6 +68,12 @@
     }
 
     @Override
+    @SuppressWarnings("removal")
+    public final IdentifiedObject createObject(String code) throws FactoryException {
+        return factory().createObject(code);
+    }
+
+    @Override
     public final Set<String> getAuthorityCodes(Class<? extends IdentifiedObject> type) throws FactoryException {
         return factory().getAuthorityCodes(type);
     }
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 9c0c4dc..a4498bb 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
@@ -28,6 +28,10 @@
 import org.opengis.util.FactoryException;
 import org.apache.sis.referencing.CRS;
 
+// Specific to the main and geoapi-3.1 branches:
+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;
@@ -78,12 +82,23 @@
         return factory().createGeographicCRS(code);
     }
 
+    @Deprecated(since = "2.0")  // Temporary version number until this branch is released.
+    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);
+    }
+
+    @Override
     public ProjectedCRS createProjectedCRS(String code) throws FactoryException {
         return factory().createProjectedCRS(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 399d5dd..33dc34e 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
@@ -27,6 +27,9 @@
 import org.opengis.util.FactoryException;
 import org.apache.sis.referencing.CRS;
 
+// 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;
 
@@ -75,6 +78,12 @@
     }
 
     @Override
+    @Deprecated(since = "1.5")
+    public ImageDatum createImageDatum(String code) throws FactoryException {
+        return factory().createImageDatum(code);
+    }
+
+    @Override
     public TemporalDatum createTemporalDatum(String code) throws FactoryException {
         return factory().createTemporalDatum(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 eb27eb4..ca37336 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,8 +29,6 @@
 import org.apache.sis.referencing.cs.DefaultCoordinateSystemAxis;
 import org.apache.sis.referencing.privy.ReferencingUtilities;
 
-// Specific to the main and geoapi-4.0 branches:
-
 
 /**
  * Utilities related to version 1 of Well Known Text format, or to ISO 19111:2007.
@@ -54,14 +52,6 @@
     public static final String DERIVED_TYPE_KEY = "derivedType";
 
     /**
-     * The "other" direction used in WKT 1 definition of geocentric CRS.
-     * It was used for meaning "toward prime meridian".
-     *
-     * @see org.apache.sis.referencing.privy.AxisDirections#isLegacyOther(AxisDirection)
-     */
-    public static final AxisDirection OTHER = AxisDirection.valueOf("OTHER");
-
-    /**
      * A three-dimensional Cartesian CS with the legacy set of geocentric axes.
      * OGC 01-009 defines the default geocentric axes as:
      *
@@ -73,8 +63,9 @@
      * 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",               OTHER, Units.METRE),
+            new DefaultCoordinateSystemAxis(Map.of(NAME_KEY, "X"), "X", AxisDirection.OTHER, Units.METRE),
             new DefaultCoordinateSystemAxis(Map.of(NAME_KEY, "Y"), "Y", AxisDirection.EAST,  Units.METRE),
             new DefaultCoordinateSystemAxis(Map.of(NAME_KEY, "Z"), "Z", AxisDirection.NORTH, Units.METRE));
 
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/RTreeNode.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/RTreeNode.java
index d513113..6b699e6 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/RTreeNode.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/RTreeNode.java
@@ -30,8 +30,8 @@
 import org.apache.sis.util.collection.TableColumn;
 import org.apache.sis.util.collection.DefaultTreeTable;
 
-// Specific to the geoapi-4.0 branch:
-import org.opengis.coordinate.MismatchedCoordinateMetadataException;
+// Specific to the main and geoapi-3.1 branches:
+import org.apache.sis.geometry.MismatchedReferenceSystemException;
 
 
 /**
@@ -251,7 +251,7 @@
                 if (common == null) {
                     common = crs;
                 } else if (crs != null && !Utilities.equalsIgnoreMetadata(common, crs)) {
-                    throw new MismatchedCoordinateMetadataException(Errors.format(Errors.Keys.MismatchedCRS));
+                    throw new MismatchedReferenceSystemException(Errors.format(Errors.Keys.MismatchedCRS));
                 }
             }
         }
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 58a22e5..b28513b 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
@@ -26,13 +26,16 @@
 import org.apache.sis.util.CharSequences;
 import org.apache.sis.measure.Units;
 
+// 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;
 
 
 /**
  * Extensions to the standard set of {@link RealizationEpoch}.
- * Some of those constants are derived from a legacy {@code VerticalDatumType} code list.
+ * Some of those constants are derived from a legacy {@link VerticalDatumType} code list.
  * 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.
  *
@@ -129,17 +132,18 @@
      * Returns the legacy code for the datum type, or 2000 (other surface) if unknown.
      * This method is used for WKT 1 formatting.
      *
-     * @param  method  the realization method, or {@code null} if unknown.
+     * @param  type  the vertical datum type, or {@code null} if unknown.
      * @return the legacy code for the given datum type, or 0 if unknown.
      */
-    public static int toLegacy(final RealizationMethod method) {
-        if (method != null) {
-            switch (method.name().toUpperCase(Locale.US)) {
+    @SuppressWarnings("deprecation")
+    public static int toLegacy(final VerticalDatumType type) {
+        if (type != null) {
+            switch (type.name().toUpperCase(Locale.US)) {
                 case ORTHOMETRIC: return 2001;      // CS_VD_Orthometric
                 case ELLIPSOIDAL: return 2002;      // CS_VD_Ellipsoidal
                 case BAROMETRIC:  return 2003;      // CS_VD_AltitudeBarometric
-                case "GEOID":     return 2005;      // CS_VD_GeoidModelDerived
-                case "TIDAL":     return 2006;      // CS_VD_Depth
+                case "GEOIDAL":   return 2005;      // CS_VD_GeoidModelDerived
+                case "DEPTH":     return 2006;      // CS_VD_Depth
             }
         }
         return 2000;
@@ -152,15 +156,16 @@
      * 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 name (never null).
+     * @return the vertical datum type (never null).
      */
-    public static String toName(final RealizationMethod method) {
-        if (method == RealizationMethod.GEOID) return "geoidal";
-        if (method == RealizationMethod.TIDAL) return "depth";
+    @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 method.name().toLowerCase(Locale.US);
+            return VerticalDatumType.valueOf(method.name().toUpperCase(Locale.US));
         }
-        return "other surface";
+        return VerticalDatumType.OTHER_SURFACE;
     }
 
     /**
@@ -170,12 +175,15 @@
      * @param  type  the vertical datum type, or {@code null}.
      * @return the realization method, or {@code null} if none.
      */
-    public static RealizationMethod fromName(final String type) {
-        if ("GEOIDAL"  .equalsIgnoreCase(type)) return RealizationMethod.GEOID;
-        if ("DEPTH"    .equalsIgnoreCase(type)) return RealizationMethod.TIDAL;
-        if (BAROMETRIC .equalsIgnoreCase(type)) return RealizationMethod.valueOf(BAROMETRIC);
-        if (ORTHOMETRIC.equalsIgnoreCase(type)) return RealizationMethod.valueOf(ORTHOMETRIC);
-        if (ELLIPSOIDAL.equalsIgnoreCase(type)) return ellipsoidal();
+    @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;
     }
 
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 038e256..4acbf9f 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
@@ -74,6 +74,9 @@
 import org.apache.sis.system.Loggers;
 import static org.apache.sis.util.Utilities.deepEquals;
 
+// 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;
@@ -420,7 +423,7 @@
         sourceCRS                   = operation.getSourceCRS();
         targetCRS                   = operation.getTargetCRS();
         interpolationCRS            = operation.getInterpolationCRS().orElse(null);
-        operationVersion            = operation.getOperationVersion().orElse(null);
+        operationVersion            = operation.getOperationVersion();
         coordinateOperationAccuracy = operation.getCoordinateOperationAccuracy();
         transform                   = operation.getMathTransform();
         if (operation instanceof AbstractCoordinateOperation) {
@@ -491,6 +494,7 @@
      *
      * @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:
@@ -498,9 +502,9 @@
          *   - DerivedCRS.getConversionFromBase() return type is Conversion.
          */
         return (sourceCRS == null && targetCRS == null)
-               || ((targetCRS instanceof DerivedCRS)
-                    && ((DerivedCRS) targetCRS).getBaseCRS() == sourceCRS
-                    && ((DerivedCRS) targetCRS).getConversionFromBase() == this);
+               || ((targetCRS instanceof GeneralDerivedCRS)
+                    && ((GeneralDerivedCRS) targetCRS).getBaseCRS() == sourceCRS
+                    && ((GeneralDerivedCRS) targetCRS).getConversionFromBase() == this);
     }
 
     /**
@@ -553,11 +557,11 @@
      * nature of the parameters. In principle this property is irrelevant to coordinate
      * {@linkplain DefaultConversion conversions}, but Apache SIS accepts it anyway.
      *
-     * @return the coordinate operation version.
+     * @return the coordinate operation version, or {@code null} if none.
      */
     @Override
-    public Optional<String> getOperationVersion() {
-        return Optional.ofNullable(operationVersion);
+    public String getOperationVersion() {
+        return operationVersion;
     }
 
     /**
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 39a7d78..92beac6 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
@@ -70,6 +70,9 @@
 import org.apache.sis.util.resources.Vocabulary;
 import static org.apache.sis.util.Utilities.equalsIgnoreMetadata;
 
+// Specific to the main and geoapi-3.1 branches:
+import org.apache.sis.util.privy.TemporalDate;
+
 
 /**
  * Finds a conversion or transformation path from a source CRS to a target CRS.
@@ -227,6 +230,7 @@
      * @since 1.0
      */
     @Override
+    @SuppressWarnings("deprecation")
     public List<CoordinateOperation> createOperations(final CoordinateReferenceSystem sourceCRS,
                                                       final CoordinateReferenceSystem targetCRS)
             throws FactoryException
@@ -277,10 +281,10 @@
         ////                       Derived  →  any Single CRS                       ////
         ////                                                                        ////
         ////////////////////////////////////////////////////////////////////////////////
-        if (sourceCRS instanceof DerivedCRS) {
-            final var source = (DerivedCRS) sourceCRS;
-            if (targetCRS instanceof DerivedCRS) {
-                return createOperationStep(source, (DerivedCRS) targetCRS);
+        if (sourceCRS instanceof GeneralDerivedCRS) {
+            final var source = (GeneralDerivedCRS) sourceCRS;
+            if (targetCRS instanceof GeneralDerivedCRS) {
+                return createOperationStep(source, (GeneralDerivedCRS) targetCRS);
             }
             if (targetCRS instanceof SingleCRS) {
                 return createOperationStep(source, (SingleCRS) targetCRS);
@@ -291,8 +295,8 @@
         ////                       any Single CRS  →  Derived                       ////
         ////                                                                        ////
         ////////////////////////////////////////////////////////////////////////////////
-        if (targetCRS instanceof DerivedCRS) {
-            final var target = (DerivedCRS) targetCRS;
+        if (targetCRS instanceof GeneralDerivedCRS) {
+            final var target = (GeneralDerivedCRS) targetCRS;
             if (sourceCRS instanceof SingleCRS) {
                 return createOperationStep((SingleCRS) sourceCRS, target);
             }
@@ -385,8 +389,9 @@
      * @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 DerivedCRS targetCRS)
+                                                            final GeneralDerivedCRS targetCRS)
             throws FactoryException
     {
         final List<CoordinateOperation> operations = createOperations(sourceCRS, targetCRS.getBaseCRS());
@@ -421,7 +426,8 @@
      * @return a coordinate operation from {@code sourceCRS} to {@code targetCRS}.
      * @throws FactoryException if the operation cannot be constructed.
      */
-    protected List<CoordinateOperation> createOperationStep(final DerivedCRS sourceCRS,
+    @SuppressWarnings("deprecation")
+    protected List<CoordinateOperation> createOperationStep(final GeneralDerivedCRS sourceCRS,
                                                             final SingleCRS targetCRS)
             throws FactoryException
     {
@@ -463,8 +469,9 @@
      * @return a coordinate operation from {@code sourceCRS} to {@code targetCRS}.
      * @throws FactoryException if the operation cannot be constructed.
      */
-    protected List<CoordinateOperation> createOperationStep(final DerivedCRS sourceCRS,
-                                                            final DerivedCRS targetCRS)
+    @SuppressWarnings("deprecation")
+    protected List<CoordinateOperation> createOperationStep(final GeneralDerivedCRS sourceCRS,
+                                                            final GeneralDerivedCRS targetCRS)
             throws FactoryException
     {
         final List<CoordinateOperation> operations = createOperations(sourceCRS.getBaseCRS(), targetCRS.getBaseCRS());
@@ -866,7 +873,9 @@
          * This "epoch shift" is in units of `targetCRS`.
          */
         final Unit<Time> targetUnit = targetCS.getAxis(0).getUnit().asType(Time.class);
-        DoubleDouble epochShift = DoubleDouble.of(Duration.between(targetDatum.getOrigin(), sourceDatum.getOrigin()));
+        DoubleDouble epochShift = DoubleDouble.of(Duration.between(
+                TemporalDate.toTemporal(targetDatum.getOrigin()),
+                TemporalDate.toTemporal(sourceDatum.getOrigin())));
         epochShift = DoubleDouble.of(Units.NANOSECOND.getConverterTo(targetUnit).convert(epochShift), true);
         /*
          * Check axis directions. The method `swapAndScaleAxes` should returns a matrix of size 2×2.
@@ -989,8 +998,23 @@
             final int numTrailingCoordinates = remainingSourceDimensions - endAtDimension;
             CoordinateOperation subOperation = info.operation;
             if ((startAtDimension | numTrailingCoordinates) != 0) {
-                subOperation = new DefaultPassThroughOperation(IdentifiedObjects.getProperties(subOperation),
-                        stepSourceCRS, stepTargetCRS, subOperation, startAtDimension, numTrailingCoordinates);
+                final Map<String,?> properties = IdentifiedObjects.getProperties(subOperation);
+                /*
+                 * The DefaultPassThroughOperation constuctor expect a SingleOperation.
+                 * In most case, the 'subOperation' is already of this kind. However if
+                 * it is not, try to copy it in such object.
+                 */
+                final SingleOperation op;
+                if (SubTypes.isSingleOperation(subOperation)) {
+                    op = (SingleOperation) subOperation;
+                } else {
+                    final MathTransform subTransform = subOperation.getMathTransform();
+                    op = factorySIS.createSingleOperation(properties,
+                            subOperation.getSourceCRS(), subOperation.getTargetCRS(), null,
+                            new DefaultOperationMethod(subTransform), subTransform);
+                }
+                subOperation = new DefaultPassThroughOperation(properties, stepSourceCRS, stepTargetCRS,
+                        op, startAtDimension, numTrailingCoordinates);
             }
             /*
              * Concatenate the operation with the ones we have found so far, and use the current `stepTargetCRS`
@@ -1113,7 +1137,7 @@
             if (isAxisChange1 && mt1.getSourceDimensions() == mt1.getTargetDimensions()) main = step2;
             if (isAxisChange2 && mt2.getSourceDimensions() == mt2.getTargetDimensions()) main = step1;
         }
-        if (main instanceof SingleOperation) {
+        if (SubTypes.isSingleOperation(main)) {
             final SingleOperation op = (SingleOperation) main;
             final MathTransform mt = factorySIS.getMathTransformFactory().createConcatenatedTransform(mt1, mt2);
             main = createFromMathTransform(new HashMap<>(IdentifiedObjects.getProperties(main)),
@@ -1267,7 +1291,8 @@
      * @param  crs  the CRS having a conversion that cannot be inverted.
      * @return a default error message.
      */
-    private String canNotInvert(final DerivedCRS crs) {
+    @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 85ef73d..f2c0519 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
@@ -80,8 +80,8 @@
 import org.apache.sis.util.collection.BackingStoreException;
 import org.apache.sis.util.resources.Vocabulary;
 
-// Specific to the geoapi-4.0 branch:
-import org.opengis.referencing.crs.DerivedCRS;
+// Specific to the main and geoapi-3.1 branches:
+import org.opengis.referencing.crs.GeneralDerivedCRS;
 
 
 /**
@@ -426,8 +426,9 @@
      * 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 DerivedCRS) {
+        if (crs instanceof GeneralDerivedCRS) {
             return false;
         }
         if (crs instanceof CompoundCRS) {
@@ -767,7 +768,7 @@
     private CoordinateOperation inverse(final CoordinateOperation operation)
             throws NoninvertibleTransformException, FactoryException
     {
-        if (operation instanceof SingleOperation) {
+        if (SubTypes.isSingleOperation(operation)) {
             return inverse((SingleOperation) operation);
         }
         CoordinateOperation inverse = AbstractCoordinateOperation.getCachedInverse(operation);
@@ -987,7 +988,7 @@
          * For example the "Affine" set of parameters depend on the number of dimensions.
          * The capability to resize an operation method is specific to Apache SIS.
          */
-        if (operation instanceof SingleOperation) {
+        if (SubTypes.isSingleOperation(operation)) {
             final SingleOperation single = (SingleOperation) operation;
             properties.put(CoordinateOperations.PARAMETERS_KEY, single.getParameterValues());
             if (method == null) {
@@ -1138,7 +1139,7 @@
              */
             Matrix matrix = MathTransforms.getMatrix(op.getMathTransform());
             if (matrix == null) {
-                if (op instanceof SingleOperation) {
+                if (SubTypes.isSingleOperation(op)) {
                     final MathTransformFactory mtFactory = factorySIS.getMathTransformFactory();
                     if (mtFactory instanceof DefaultMathTransformFactory) {
                         if (forward) sourceCRS = toGeodetic3D(sourceCRS, source3D);
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultConcatenatedOperation.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultConcatenatedOperation.java
index d87f205..908e6a6 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultConcatenatedOperation.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultConcatenatedOperation.java
@@ -48,6 +48,9 @@
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.io.wkt.Formatter;
 
+// Specific to the main and geoapi-3.1 branches:
+import org.opengis.referencing.operation.SingleOperation;
+
 
 /**
  * An ordered sequence of two or more single coordinate operations. The sequence of operations is constrained
@@ -90,7 +93,7 @@
      * This field is modified only at unmarshalling time by {@link #setSteps(CoordinateOperation[])}</p>
      */
     @SuppressWarnings("serial")         // Most SIS implementations are serializable.
-    private List<? extends CoordinateOperation> operations;
+    private List<SingleOperation> operations;
 
     /**
      * Constructs a concatenated operation from a set of properties and a
@@ -182,7 +185,7 @@
          * At this point we should have flattened.size() >= 2, except if some operations
          * were omitted because their associated math transform were identity operation.
          */
-        this.operations = UnmodifiableArrayList.wrap(flattened.toArray(CoordinateOperation[]::new));
+        this.operations = UnmodifiableArrayList.wrap(flattened.toArray(SingleOperation[]::new));
     }
 
     /**
@@ -406,11 +409,17 @@
      * The sequence can contain {@link org.opengis.referencing.operation.SingleOperation}s
      * or {@link org.opengis.referencing.operation.PassThroughOperation}s.
      *
+     * <div class="warning"><b>Upcoming API change</b><br>
+     * This method is conformant to ISO 19111:2003. But the ISO 19111:2007 revision changed the element type
+     * from {@code SingleOperation} to {@link CoordinateOperation}. This change may be applied in GeoAPI 4.0.
+     * This is necessary for supporting usage of {@code PassThroughOperation} with {@link ConcatenatedOperation}.
+     * </div>
+     *
      * @return the sequence of operations.
      */
     @Override
     @SuppressWarnings("ReturnOfCollectionOrArrayField")
-    public List<? extends CoordinateOperation> getOperations() {
+    public List<SingleOperation> getOperations() {
         return operations;
     }
 
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 b3d96d3..8e5f601 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
@@ -39,8 +39,11 @@
 import org.apache.sis.util.Utilities;
 import org.apache.sis.util.resources.Errors;
 
-// Specific to the geoapi-4.0 branch:
-import org.opengis.referencing.crs.DerivedCRS;
+// Specific to the main and geoapi-3.1 branches:
+import org.opengis.referencing.operation.Projection;
+import org.opengis.referencing.crs.GeneralDerivedCRS;
+import org.opengis.referencing.crs.GeographicCRS;
+import org.opengis.referencing.crs.ProjectedCRS;
 
 
 /**
@@ -220,10 +223,11 @@
      * @param target      the new target CRS.
      * @param factory     the factory to use for creating a transform from the parameters or for performing axis changes.
      */
-    private DefaultConversion(final Conversion definition,
-                              final CoordinateReferenceSystem source,
-                              final CoordinateReferenceSystem target,
-                              final MathTransformFactory factory) throws FactoryException
+    @SuppressWarnings("deprecation")
+    DefaultConversion(final Conversion definition,
+                      final CoordinateReferenceSystem source,
+                      final CoordinateReferenceSystem target,
+                      final MathTransformFactory factory) throws FactoryException
     {
         super(definition);
         int interpDim = ReferencingUtilities.getDimension(super.getInterpolationCRS().orElse(null));
@@ -245,7 +249,7 @@
                  * method in invoked, and attempts to use it can cause NullPointerException.
                  */
                 final DefaultMathTransformFactory.Context context;
-                if (target instanceof DerivedCRS) {
+                if (target instanceof GeneralDerivedCRS) {
                     context = ReferencingUtilities.createTransformContext(source, null);
                     context.setTarget(target.getCoordinateSystem());    // Using `target` would be unsafe here.
                 } else {
@@ -328,6 +332,9 @@
     public static DefaultConversion castOrCopy(final Conversion object) {
         if (object == null || object instanceof DefaultConversion) {
             return (DefaultConversion) object;
+        }
+        if (object instanceof Projection) {
+            return new DefaultProjection((Projection) object);
         } else {
             return new DefaultConversion(object);
         }
@@ -365,6 +372,7 @@
      *
      * @since 1.5
      */
+    @SuppressWarnings("deprecation")
     public Conversion specialize(final CoordinateReferenceSystem sourceCRS,
                                  final CoordinateReferenceSystem targetCRS,
                                  MathTransformFactory factory) throws FactoryException
@@ -377,7 +385,7 @@
          * a dedicated kind of operations, namely Transformation.
          */
         ensureCompatibleDatum("sourceCRS", super.getSourceCRS(), sourceCRS);
-        if (!(targetCRS instanceof DerivedCRS)) {
+        if (!(targetCRS instanceof GeneralDerivedCRS)) {
             ensureCompatibleDatum("targetCRS", super.getTargetCRS(), targetCRS);
         } else {
             /*
@@ -391,15 +399,20 @@
              */
             ensureCompatibleDatum("targetCRS", sourceCRS, super.getTargetCRS());
         }
+        final boolean isProjection = (targetCRS instanceof ProjectedCRS) && (sourceCRS instanceof GeographicCRS);
         if (super.getSourceCRS() == sourceCRS &&
             super.getTargetCRS() == targetCRS &&
-            super.getMathTransform() != null)
+            super.getMathTransform() != null &&
+            isProjection == (this instanceof Projection))
         {
             return this;
         }
         if (factory == null) {
             factory = DefaultMathTransformFactory.provider();
         }
+        if (isProjection) {
+            return new DefaultProjection(this, sourceCRS, targetCRS, factory);
+        }
         return new DefaultConversion(this, sourceCRS, targetCRS, factory);
     }
 
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 65fbd9c..d5b622b 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
@@ -56,6 +56,10 @@
 import org.apache.sis.util.iso.AbstractFactory;
 import org.apache.sis.util.resources.Errors;
 
+// Specific to the main and geoapi-3.1 branches:
+import org.opengis.referencing.crs.GeographicCRS;
+import org.opengis.referencing.crs.ProjectedCRS;
+
 
 /**
  * Creates {@linkplain AbstractCoordinateOperation operations} capable to transform coordinates
@@ -554,7 +558,13 @@
          */
         if (baseType == SingleOperation.class) {
             if (isConversion(sourceCRS, targetCRS)) {
-                baseType = Conversion.class;
+                if (interpolationCRS == null && sourceCRS instanceof GeographicCRS
+                                             && targetCRS instanceof ProjectedCRS)
+                {
+                    baseType = Projection.class;
+                } else {
+                    baseType = Conversion.class;
+                }
             } else {
                 // TODO: handle point motion operation.
                 baseType = Transformation.class;
@@ -573,6 +583,14 @@
         final AbstractSingleOperation op;
         if (Transformation.class.isAssignableFrom(baseType)) {
             op = new DefaultTransformation(properties, sourceCRS, targetCRS, interpolationCRS, method, transform);
+        } else if (Projection.class.isAssignableFrom(baseType)) {
+            ArgumentChecks.ensureCanCast("sourceCRS", GeographicCRS.class, sourceCRS);
+            ArgumentChecks.ensureCanCast("targetCRS", ProjectedCRS .class, targetCRS);
+            if (interpolationCRS != null) {
+                throw new IllegalArgumentException(Errors.format(
+                        Errors.Keys.ForbiddenAttribute_2, "interpolationCRS", baseType));
+            }
+            op = new DefaultProjection(properties, (GeographicCRS) sourceCRS, (ProjectedCRS) targetCRS, method, transform);
         } else if (Conversion.class.isAssignableFrom(baseType)) {
             op = new DefaultConversion(properties, sourceCRS, targetCRS, interpolationCRS, method, transform);
         } else {  // See above comment about this last-resort fallback.
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 85951ff..0980d5a 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
@@ -57,8 +57,9 @@
 import org.apache.sis.io.wkt.ElementKind;
 import org.apache.sis.io.wkt.FormattableObject;
 
-// Specific to the geoapi-4.0 branch:
-import org.opengis.referencing.crs.DerivedCRS;
+// Specific to the main and geoapi-3.1 branches:
+import jakarta.xml.bind.annotation.XmlSchemaType;
+import org.opengis.referencing.crs.GeneralDerivedCRS;
 
 
 /**
@@ -127,6 +128,8 @@
 @XmlType(name = "OperationMethodType", propOrder = {
     "formulaCitation",
     "formulaDescription",
+    "sourceDimensions",
+    "targetDimensions",
     "descriptors"
 })
 @XmlRootElement(name = "OperationMethod")
@@ -377,6 +380,42 @@
     }
 
     /**
+     * Number of dimensions in the source CRS of this operation method.
+     * May be null if unknown, as in an <i>Affine Transform</i>.
+     *
+     * @return the dimension of source CRS, or {@code null} if unknown.
+     *
+     * @see org.apache.sis.referencing.operation.transform.AbstractMathTransform#getSourceDimensions()
+     *
+     * @deprecated This attribute has been removed from ISO 19111:2019.
+     */
+    @Override
+    @Deprecated(since="1.1")
+    @XmlElement(name = "sourceDimensions")
+    @XmlSchemaType(name = "positiveInteger")
+    public Integer getSourceDimensions() {
+        return null;
+    }
+
+    /**
+     * Number of dimensions in the target CRS of this operation method.
+     * May be null if unknown, as in an <i>Affine Transform</i>.
+     *
+     * @return the dimension of target CRS, or {@code null} if unknown.
+     *
+     * @see org.apache.sis.referencing.operation.transform.AbstractMathTransform#getTargetDimensions()
+     *
+     * @deprecated This attribute has been removed from ISO 19111:2019.
+     */
+    @Override
+    @Deprecated(since="1.1")
+    @XmlElement(name = "targetDimensions")
+    @XmlSchemaType(name = "positiveInteger")
+    public Integer getTargetDimensions() {
+        return null;
+    }
+
+    /**
      * Returns the set of parameters.
      *
      * <h4>Departure from the ISO 19111 standard</h4>
@@ -449,7 +488,9 @@
                 }
             }
             final OperationMethod that = (OperationMethod) object;
-            return Utilities.deepEquals(getParameters(), that.getParameters(), mode);
+            return Objects.equals(getSourceDimensions(), that.getSourceDimensions()) &&
+                   Objects.equals(getTargetDimensions(), that.getTargetDimensions()) &&
+                   Utilities.deepEquals(getParameters(), that.getParameters(), mode);
         }
         return false;
     }
@@ -474,6 +515,7 @@
      * @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;
         /*
@@ -499,8 +541,8 @@
              * CRS is either the immediate parent in WKT 1, or the parent of the parent in WKT 2.
              */
             final FormattableObject parent = formatter.getEnclosingElement(isWKT1 ? 1 : 2);
-            if (parent instanceof DerivedCRS) {
-                final Conversion conversion = ((DerivedCRS) parent).getConversionFromBase();
+            if (parent instanceof GeneralDerivedCRS) {
+                final Conversion conversion = ((GeneralDerivedCRS) parent).getConversionFromBase();
                 if (conversion != null) {   // Should never be null, but let be safe.
                     final ParameterDescriptorGroup descriptor;
                     if (conversion instanceof Parameterized) {  // Usual case in SIS implementation.
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultPassThroughOperation.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultPassThroughOperation.java
index 762e328..fdcc480 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultPassThroughOperation.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultPassThroughOperation.java
@@ -42,6 +42,11 @@
 import org.apache.sis.io.wkt.Formatter;
 import static org.apache.sis.util.Utilities.deepEquals;
 
+// Specific to the main and geoapi-3.1 branches:
+import org.opengis.referencing.operation.OperationMethod;
+import org.opengis.referencing.operation.SingleOperation;
+import org.opengis.parameter.ParameterValueGroup;
+
 
 /**
  * Specifies that a subset of a coordinate tuple is subject to a specific coordinate operation.
@@ -65,12 +70,12 @@
      * The operation to apply on the subset of a coordinate tuple.
      *
      * <p><b>Consider this field as final!</b>
-     * This field is modified only at unmarshalling time by {@link #setOperation(CoordinateOperation)}</p>
+     * This field is modified only at unmarshalling time by {@code setOperation(CoordinateOperation)}</p>
      *
      * @see #getOperation()
      */
     @SuppressWarnings("serial")                 // Most SIS implementations are serializable.
-    private CoordinateOperation operation;
+    private SingleOperation operation;
 
     /**
      * Zero-based indices of the modified source coordinates.
@@ -116,7 +121,7 @@
     public DefaultPassThroughOperation(final Map<String,?>            properties,
                                        final CoordinateReferenceSystem sourceCRS,
                                        final CoordinateReferenceSystem targetCRS,
-                                       final CoordinateOperation       operation,
+                                       final SingleOperation           operation,
                                        final int firstAffectedCoordinate,
                                        final int numTrailingCoordinates)
     {
@@ -130,7 +135,7 @@
     private DefaultPassThroughOperation(final Map<String,?>            properties,
                                         final CoordinateReferenceSystem sourceCRS,
                                         final CoordinateReferenceSystem targetCRS,
-                                        final CoordinateOperation       operation,
+                                        final SingleOperation           operation,
                                         final MathTransform          subTransform,
                                         final int firstAffectedCoordinate,
                                         final int numTrailingCoordinates)
@@ -193,15 +198,43 @@
     }
 
     /**
+     * @deprecated May be removed in GeoAPI 4.0 since it does not apply to pass-through operations.
+     *
+     * @return {@code null}.
+     */
+    @Override
+    @Deprecated
+    public OperationMethod getMethod() {
+        return null;
+    }
+
+    /**
+     * @deprecated May be removed in GeoAPI 4.0 since it does not apply to pass-through operations.
+     *
+     * @return {@code null}.
+     */
+    @Override
+    @Deprecated
+    public ParameterValueGroup getParameterValues() {
+        return null;
+    }
+
+    /**
      * Returns the operation to apply on the subset of a coordinate tuple.
      *
+     * <div class="warning"><b>Upcoming API change</b><br>
+     * This method is conformant to ISO 19111:2003. But the ISO 19111:2007 revision changed the type from
+     * {@code SingleOperation} to {@link CoordinateOperation}. This change may be applied in GeoAPI 4.0.
+     * This is necessary for supporting usage of {@code PassThroughOperation} with {@code ConcatenatedOperation}.
+     * </div>
+     *
      * @return the operation to apply on the subset of a coordinate tuple.
      *
      * @see PassThroughTransform#getSubTransform()
      */
     @Override
     @XmlElement(name = "coordOperation", required = true)
-    public CoordinateOperation getOperation() {
+    public SingleOperation getOperation() {
         return operation;
     }
 
@@ -316,7 +349,7 @@
      *
      * @see #getOperation()
      */
-    private void setOperation(final CoordinateOperation op) {
+    private void setOperation(final SingleOperation op) {
         if (operation == null) {
             operation = op;
         } else {
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultProjection.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultProjection.java
new file mode 100644
index 0000000..b8243c7
--- /dev/null
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultProjection.java
@@ -0,0 +1,109 @@
+/*
+ * 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;
+
+import java.util.Map;
+import jakarta.xml.bind.annotation.XmlTransient;
+import org.opengis.util.FactoryException;
+import org.opengis.referencing.operation.Conversion;
+import org.opengis.referencing.operation.Projection;
+import org.opengis.referencing.operation.OperationMethod;
+import org.opengis.referencing.operation.MathTransform;
+import org.opengis.referencing.operation.MathTransformFactory;
+import org.opengis.referencing.crs.ProjectedCRS;
+import org.opengis.referencing.crs.GeographicCRS;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.apache.sis.util.ArgumentChecks;
+
+
+/**
+ * A conversion from (<var>longitude</var>, <var>latitude</var>) coordinates to Cartesian coordinates
+ * (<var>x</var>,<var>y</var>).
+ *
+ * @author  Martin Desruisseaux (IRD, Geomatys)
+ *
+ * @see org.apache.sis.referencing.crs.DefaultProjectedCRS
+ */
+@XmlTransient
+final class DefaultProjection extends DefaultConversion implements Projection {
+    /**
+     * Serial number for inter-operability with different versions.
+     */
+    private static final long serialVersionUID = -7176751851369816864L;
+
+    /**
+     * Creates a projection from the given properties.
+     *
+     * @param  properties  the properties to be given to the identified object.
+     * @param  sourceCRS   the source CRS.
+     * @param  targetCRS   the target CRS.
+     * @param  method      the coordinate operation method.
+     * @param  transform   transform from positions in the source CRS to positions in the target CRS.
+     */
+    public DefaultProjection(final Map<String,?>   properties,
+                             final GeographicCRS   sourceCRS,
+                             final ProjectedCRS    targetCRS,
+                             final OperationMethod method,
+                             final MathTransform   transform)
+    {
+        super(properties, sourceCRS, targetCRS, null, method, transform);
+    }
+
+    /**
+     * Creates a new projection with the same values as the specified one, together with the
+     * specified source and target CRS. While the source conversion can be an arbitrary one,
+     * it is typically a defining conversion.
+     *
+     * @param  definition  the defining conversion.
+     * @param  sourceCRS   the source CRS.
+     * @param  targetCRS   the target CRS.
+     * @param  factory     the factory to use for creating a transform from the parameters or for performing axis changes.
+     * @throws IllegalArgumentException if the source or targe CRS is not of the expected types.
+     */
+    DefaultProjection(final Conversion definition,
+                      final CoordinateReferenceSystem sourceCRS,
+                      final CoordinateReferenceSystem targetCRS,
+                      final MathTransformFactory factory) throws FactoryException
+    {
+        super(definition, sourceCRS, targetCRS, factory);
+        ArgumentChecks.ensureCanCast("sourceCRS", GeographicCRS.class, sourceCRS);
+        ArgumentChecks.ensureCanCast("targetCRS", ProjectedCRS .class, targetCRS);
+    }
+
+    /**
+     * Creates a new coordinate operation with the same values as the specified one.
+     * This copy constructor provides a way to convert an arbitrary implementation
+     * into a SIS one, 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  operation  the coordinate operation to copy.
+     */
+    protected DefaultProjection(final Projection operation) {
+        super(operation);
+    }
+
+    /**
+     * Returns the GeoAPI interface implemented by this class.
+     *
+     * @return the conversion interface implemented by this class.
+     */
+    @Override
+    public Class<? extends Projection> getInterface() {
+        return Projection.class;
+    }
+}
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 677ca42..b0a7591 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
@@ -95,7 +95,7 @@
         }
         boolean useSameParameters = false;
         for (final GeneralParameterDescriptor descriptor : method.getParameters().descriptors()) {
-            useSameParameters = (descriptor.getRemarks().orElse(null) instanceof SignReversalComment);
+            useSameParameters = (descriptor.getRemarks() instanceof SignReversalComment);
             if (!useSameParameters) break;
         }
         if (useSameParameters) {
@@ -106,7 +106,7 @@
         final var properties = new HashMap<String,Object>(6);
         properties.put(NAME_KEY,    name);
         properties.put(FORMULA_KEY, method.getFormula());
-        method.getRemarks().ifPresent((remarks) -> properties.put(REMARKS_KEY, remarks));
+        properties.put(REMARKS_KEY, method.getRemarks());
         if (method instanceof Deprecable) {
             properties.put(DEPRECATED_KEY, ((Deprecable) method).isDeprecated());
         }
@@ -157,7 +157,7 @@
                 final Object value = src.getValue();
                 if (value instanceof Number) {
                     final ParameterDescriptor<?> descriptor = src.getDescriptor();
-                    final InternationalString remarks = descriptor.getRemarks().orElse(null);
+                    final InternationalString remarks = descriptor.getRemarks();
                     if (remarks != SignReversalComment.SAME) {
                         if (remarks != SignReversalComment.OPPOSITE) {
                             /*
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/LooselyDefinedMethod.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/LooselyDefinedMethod.java
index ce94302..b8a11e9 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/LooselyDefinedMethod.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/LooselyDefinedMethod.java
@@ -71,7 +71,7 @@
         final var parameters = new DefaultParameterDescriptorGroup(properties, 0, 1);
 
         properties.put(DefaultOperationMethod.NAME_KEY,    "Affine parametric transformation in geocentric domain");
-        properties.put(DefaultOperationMethod.REMARKS_KEY, parameters.getRemarks().get());
+        properties.put(DefaultOperationMethod.REMARKS_KEY, parameters.getRemarks());
         properties.put(DefaultOperationMethod.FORMULA_KEY, new DefaultFormula(
                 "This operation method is currently an implementation dependent black box. " +
                 "A future version may redefine this method in terms of more standard methods."));
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 277276c..2f0075b 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,9 +25,6 @@
 import org.apache.sis.referencing.operation.matrix.Matrices;
 import org.apache.sis.referencing.operation.matrix.MatrixSIS;
 
-// Specific to the geoapi-4.0 branch:
-import org.apache.sis.referencing.crs.DefaultImageCRS;
-
 
 /**
  * Information about the operation from a source component to a target component in {@code CompoundCRS} instances.
@@ -54,22 +51,24 @@
      * {@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},
         {EngineeringCRS.class},
-        {DefaultImageCRS.class}
+        {ImageCRS.class}
     };
 
     /**
      * 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 DerivedCRS) {
-            crs = ((DerivedCRS) crs).getBaseCRS();
+        while (crs instanceof GeneralDerivedCRS) {
+            crs = (SingleCRS) ((GeneralDerivedCRS) crs).getBaseCRS();
         }
         return crs.getClass();
     }
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/SubTypes.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/SubTypes.java
index d7e8ce8..52f47cf 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/SubTypes.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/SubTypes.java
@@ -39,6 +39,17 @@
     }
 
     /**
+     * Returns {@code true} if the given operation is a single operation but not a pass-through operation.
+     * In an older ISO 19111 model, {@link PassThroughOperation} extended {@link SingleOperation}, which
+     * was a problem for providing a value to the inherited {@link SingleOperation#getMethod()} method.
+     * This has been fixed in newer ISO 19111 model, but for safety with objects following the older model
+     * (e.g. GeoAPI 3.0) we are better to perform an explicit exclusion of {@link PassThroughOperation}.
+     */
+    static boolean isSingleOperation(final CoordinateOperation operation) {
+        return (operation instanceof SingleOperation) && !(operation instanceof PassThroughOperation);
+    }
+
+    /**
      * Returns a SIS implementation for the given coordinate operation.
      *
      * @see AbstractCoordinateOperation#castOrCopy(CoordinateOperation)
@@ -56,7 +67,7 @@
         if (object instanceof ConcatenatedOperation) {
             return DefaultConcatenatedOperation.castOrCopy((ConcatenatedOperation) object);
         }
-        if (object instanceof SingleOperation) {
+        if (isSingleOperation(object)) {
             return (object instanceof AbstractSingleOperation) ? (AbstractSingleOperation) object
                    : new AbstractSingleOperation((SingleOperation) object);
         }
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 ea18879..38c0cfb 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
@@ -411,7 +411,8 @@
             return;
         }
         if (actual != expected) {
-            throw new MismatchedDimensionException(Errors.format(Errors.Keys.MismatchedDimension_3, "source", expected, actual));
+            throw new org.opengis.geometry.MismatchedDimensionException(
+                    Errors.format(Errors.Keys.MismatchedDimension_3, "source", expected, actual));
         }
     }
 
@@ -421,8 +422,9 @@
      * 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 MismatchedDimensionException(Errors.format(Errors.Keys.MismatchedDimension_3,
-                    Strings.toIndexed(name, numPoints), expected, actual));
+        return new org.opengis.geometry.MismatchedDimensionException(
+                Errors.format(Errors.Keys.MismatchedDimension_3,
+                Strings.toIndexed(name, numPoints), expected, actual));
     }
 
     /**
@@ -571,7 +573,7 @@
         }
         final int srcDim = gridToCRS.getSourceDimensions();
         if (srcDim != gridSize.length) {
-            throw new MismatchedDimensionException(Errors.format(
+            throw new org.opengis.geometry.MismatchedDimensionException(Errors.format(
                     Errors.Keys.MismatchedTransformDimension_4, "gridToCRS", 0, gridSize.length, srcDim));
         }
         final int tgtDim = gridToCRS.getTargetDimensions();
@@ -969,7 +971,7 @@
         verifySourceDimension(source.length);
         final int tgtDim = target.length;
         if (targets != null && tgtDim != targets.length) {
-            throw new MismatchedDimensionException(Errors.format(
+            throw new org.opengis.geometry.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 4378d60..fba1687 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
@@ -383,8 +383,9 @@
                 return;
             }
         }
-        throw new MismatchedDimensionException(Errors.format(Errors.Keys.MismatchedTransformDimension_4,
-                                               "sourceToGrid", isTarget, SOURCE_DIMENSION, dim));
+        throw new org.opengis.geometry.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/ProjectedTransformTry.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/builder/ProjectedTransformTry.java
index 7ead80c..32e24be 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/builder/ProjectedTransformTry.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/builder/ProjectedTransformTry.java
@@ -33,8 +33,8 @@
 import org.apache.sis.referencing.operation.matrix.MatrixSIS;
 import org.apache.sis.referencing.operation.transform.MathTransforms;
 
-// Specific to the geoapi-4.0 branch:
-import org.opengis.coordinate.MismatchedDimensionException;
+// Specific to the main and geoapi-3.1 branches:
+import org.opengis.geometry.MismatchedDimensionException;
 
 
 /**
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/MapProjection.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/MapProjection.java
index b76d237..82d61a3 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/MapProjection.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/MapProjection.java
@@ -49,6 +49,9 @@
 import org.apache.sis.parameter.Parameters;
 import org.apache.sis.util.resources.Errors;
 
+// Specific to the main and geoapi-3.1 branches:
+import org.opengis.referencing.ReferenceIdentifier;
+
 
 /**
  * Base class for most two-dimensional map projection providers defined in this package.
@@ -351,12 +354,12 @@
             }
             builder.addName(alias);
         }
-        for (Identifier id : template.getIdentifiers()) {
+        for (ReferenceIdentifier id : template.getIdentifiers()) {
             final Citation authority = id.getAuthority();
             for (int i=0; i<toRename.length; i++) {
                 if (authority == toRename[i]) {
                     if (newCodes[i] == null) continue;
-                    id = newCodes[i];
+                    id = (ReferenceIdentifier) newCodes[i];
                     newCodes[i] = null;
                     break;
                 }
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/Mercator1SP.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/Mercator1SP.java
index 0f8ffa5..0053c66 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/Mercator1SP.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/Mercator1SP.java
@@ -110,7 +110,7 @@
         LATITUDE_OF_ORIGIN = createZeroConstant(builder.addNamesAndIdentifiers(Equirectangular.LATITUDE_OF_ORIGIN)
                 .reidentify(Citations.GEOTIFF, "3081")
                 .rename(Citations.GEOTIFF, "NatOriginLat")
-                .setRemarks(Equirectangular.LATITUDE_OF_ORIGIN.getRemarks().get()));
+                .setRemarks(Equirectangular.LATITUDE_OF_ORIGIN.getRemarks()));
 
         LONGITUDE_OF_ORIGIN = createLongitude(builder.addNamesAndIdentifiers(Equirectangular.LONGITUDE_OF_ORIGIN)
                 .reidentify(Citations.GEOTIFF, "3080")
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/MercatorSpherical.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/MercatorSpherical.java
index 8aa5cf1..079d290 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/MercatorSpherical.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/MercatorSpherical.java
@@ -59,7 +59,7 @@
          */
         final ParameterDescriptor<Double> scaleFactor = createScale(builder
                 .addNamesAndIdentifiers(Mercator1SP.SCALE_FACTOR)
-                .setRemarks(Mercator2SP.SCALE_FACTOR.getRemarks().get())
+                .setRemarks(Mercator2SP.SCALE_FACTOR.getRemarks())
                 .setRequired(false));
 
         PARAMETERS = addNameAndLegacy(addIdentifierAndLegacy(builder, IDENTIFIER, "9841"),
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/ObliqueMercatorCenter.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/ObliqueMercatorCenter.java
index 1fffd78..8a7ca14 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/ObliqueMercatorCenter.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/ObliqueMercatorCenter.java
@@ -23,6 +23,9 @@
 import org.apache.sis.metadata.iso.citation.Citations;
 import static org.apache.sis.referencing.IdentifiedObjects.getIdentifier;
 
+// Specific to the main and geoapi-3.1 branches:
+import org.opengis.referencing.ReferenceIdentifier;
+
 
 /**
  * The provider for <q>Hotine Oblique Mercator (variant B)</q> projection (EPSG:9815).
@@ -112,7 +115,7 @@
                 .addName      (Citations.S57,     "OME")
                 .addIdentifier(Citations.S57,     "9")
                 .addName      (Citations.GEOTIFF, "CT_ObliqueMercator")
-                .addIdentifier(getIdentifier(PARAMETERS_A, Citations.GEOTIFF))      // Same GeoTIFF identifier.
+                .addIdentifier((ReferenceIdentifier) getIdentifier(PARAMETERS_A, Citations.GEOTIFF))      // Same GeoTIFF identifier.
                 .addName      (Citations.PROJ4,   "omerc")
                 .createGroupForMapProjection(
                         LATITUDE_OF_CENTRE,
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 26b1360..f755ea0 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
@@ -286,7 +286,8 @@
      * @param  dimension  the wrong dimension.
      */
     static MismatchedDimensionException mismatchedDimension(final String argument, final int expected, final int dimension) {
-        return new MismatchedDimensionException(Errors.format(Errors.Keys.MismatchedDimension_3, argument, expected, dimension));
+        return new org.opengis.geometry.MismatchedDimensionException(
+                Errors.format(Errors.Keys.MismatchedDimension_3, argument, expected, dimension));
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/ConcatenatedTransform.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/ConcatenatedTransform.java
index a4140e7..a1e5b40 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/ConcatenatedTransform.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/ConcatenatedTransform.java
@@ -49,8 +49,8 @@
 import org.apache.sis.util.logging.Logging;
 import org.apache.sis.util.resources.Errors;
 
-// Specific to the geoapi-4.0 branch:
-import org.opengis.coordinate.MismatchedDimensionException;
+// Specific to the main and geoapi-3.1 branches:
+import org.opengis.geometry.MismatchedDimensionException;
 
 
 /**
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 8bfa59d..8048ec3 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
@@ -1690,6 +1690,19 @@
     }
 
     /**
+     * There is no XML format for math transforms.
+     *
+     * @param  xml  math transform encoded in XML format.
+     * @throws FactoryException if the object creation failed.
+     */
+    @Override
+    @Deprecated
+    public MathTransform createFromXML(String xml) throws FactoryException {
+        lastMethod.remove();
+        throw new FactoryException(Errors.format(Errors.Keys.UnsupportedOperation_1, "createFromXML"));
+    }
+
+    /**
      * Creates a math transform object from a
      * <a href="http://www.geoapi.org/snapshot/javadoc/org/opengis/referencing/doc-files/WKT.html">Well Known Text (WKT)</a>.
      * If the given text contains non-fatal anomalies (unknown or unsupported WKT elements,
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/InterpolatedGeocentricTransform.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/InterpolatedGeocentricTransform.java
index 05f730a..0f39a1e 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/InterpolatedGeocentricTransform.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/InterpolatedGeocentricTransform.java
@@ -39,8 +39,8 @@
 import org.apache.sis.referencing.operation.matrix.Matrices;
 import org.apache.sis.referencing.internal.Resources;
 
-// Specific to the geoapi-4.0 branch:
-import org.opengis.coordinate.MismatchedDimensionException;
+// Specific to the main and geoapi-3.1 branches:
+import org.opengis.geometry.MismatchedDimensionException;
 
 
 /**
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 8b2bf1d..8fedcff 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
@@ -258,8 +258,9 @@
         final int subDim = subTransform.getSourceDimensions();
         final int actual = modifiedCoordinates.cardinality();
         if (actual != subDim) {
-            throw new MismatchedDimensionException(Errors.format(Errors.Keys.MismatchedDimension_3,
-                                                   "modifiedCoordinates", subDim, actual));
+            throw new org.opengis.geometry.MismatchedDimensionException(
+                    Errors.format(Errors.Keys.MismatchedDimension_3,
+                    "modifiedCoordinates", subDim, actual));
         }
         final var sep = new TransformSeparator(subTransform, factory);
         MathTransform result = MathTransforms.identity(resultDim);
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 aa443b4..4671df6 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
@@ -103,6 +103,7 @@
     /**
      * Proposed abbreviations for some axis directions.
      */
+    @SuppressWarnings("deprecation")
     private static final Map<AxisDirection,String> ABBREVIATIONS = Map.of(
             FUTURE,            "t",
             COLUMN_POSITIVE,   "i",
@@ -110,6 +111,7 @@
             DISPLAY_RIGHT,     "x",
             DISPLAY_UP,        "y",
             UNSPECIFIED,       "m",     // Arbitrary abbreviation, may change in any future SIS version.
+            OTHER,             "m",     // Idem.
             AWAY_FROM,         "r",
             COUNTER_CLOCKWISE, "θ");
 
@@ -257,21 +259,6 @@
     }
 
     /**
-     * Returns {@code true} if the given direction is the legacy "other" code list value.
-     * The "other" direction was used in Well Known Text (WKT) 1 but is no longer declared
-     * in recent standards.
-     *
-     * @param  dir  the direction to test, or {@code null}.
-     * @return {@code true} if the given direction is the legacy "other" direction.
-     *
-     * @see org.apache.sis.referencing.internal.Legacy#OTHER
-     */
-    public static boolean isLegacyOther(final AxisDirection dir) {
-        // Compare "other" as string for avoiding class loading.
-        return (dir != null) && "OTHER".equalsIgnoreCase(dir.name());
-    }
-
-    /**
      * Returns {@code true} if the given direction is a spatial axis direction (including vertical and geocentric axes).
      * The current implementation conservatively returns {@code true} for every non-null directions except a hard-coded
      * set of directions which are known to be non-spatial. We conservatively accept unknown axis directions because
@@ -728,9 +715,6 @@
             if (abbreviation != null) {
                 return abbreviation;
             }
-            if (isLegacyOther(direction)) {
-                return "m";             // Arbitrary abbreviation, may change in any future SIS version.
-            }
         }
         final String id = direction.identifier();   // UML identifier, or null if none.
         return camelCaseToAcronym(id != null ? id : direction.name()).toString().intern();
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 d3e1d13..7c90700 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
@@ -45,8 +45,8 @@
 import org.apache.sis.util.privy.Numerics;
 import org.apache.sis.util.collection.Containers;
 
-// Specific to the geoapi-4.0 branch:
-import org.opengis.referencing.crs.DerivedCRS;
+// Specific to the main and geoapi-3.1 branches:
+import org.opengis.referencing.crs.GeneralDerivedCRS;
 
 
 /**
@@ -254,10 +254,11 @@
      * @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 DerivedCRS) {
-            source = ((DerivedCRS) source).getBaseCRS();
+        while (source instanceof GeneralDerivedCRS) {
+            source = ((GeneralDerivedCRS) source).getBaseCRS();
             changes |= changes(source.getCoordinateSystem(), target);
         }
         final boolean useCache = (changes >= 0 && changes < CACHE.length);
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 8669578..626bfe9 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
@@ -42,8 +42,8 @@
 import org.apache.sis.util.Utilities;
 import org.apache.sis.util.logging.Logging;
 
-// Specific to the geoapi-4.0 branch:
-import org.opengis.referencing.crs.DerivedCRS;
+// Specific to the main and geoapi-3.1 branches:
+import org.opengis.referencing.crs.GeneralDerivedCRS;
 
 
 /**
@@ -309,14 +309,15 @@
      * 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();
             final SingleCRS crsG = given.next();
             if (!Utilities.equalsApproximately(crsA, crsG)) {
-                if (crsA instanceof DerivedCRS && crsG instanceof DerivedCRS) {
-                    final Conversion cnvA = ((DerivedCRS) crsA).getConversionFromBase();
-                    final Conversion cnvG = ((DerivedCRS) crsG).getConversionFromBase();
+                if (crsA instanceof GeneralDerivedCRS && crsG instanceof GeneralDerivedCRS) {
+                    final Conversion cnvA = ((GeneralDerivedCRS) crsA).getConversionFromBase();
+                    final Conversion cnvG = ((GeneralDerivedCRS) crsG).getConversionFromBase();
                     if (!Utilities.equalsApproximately(cnvA, cnvG)) {
                         return Utilities.equalsApproximately(cnvA.getMethod(), cnvG.getMethod()) ? CONVERSION : METHOD;
                     }
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 c8791db..8dfe6c7 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
@@ -147,9 +147,9 @@
                         crs = factories.getCRSFactory().createGeographicCRS(crsProps, ((GeodeticCRS) crs).getDatum(), (EllipsoidalCS) cs);
                     } else {
                         final ProjectedCRS proj = (ProjectedCRS) crs;
-                        GeodeticCRS base = proj.getBaseCRS();
+                        GeographicCRS base = proj.getBaseCRS();
                         if (base.getCoordinateSystem().getDimension() == 2) {
-                            base = (GeodeticCRS) createCompoundCRS(
+                            base = (GeographicCRS) createCompoundCRS(
                                     IdentifiedObjects.getProperties(base, GeographicCRS.IDENTIFIERS_KEY), base, vertical);
                         }
                         /*
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 e02350f..778b401 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
@@ -64,9 +64,6 @@
 // Specific to the geoapi-3.1 and geoapi-4.0 branches:
 import org.opengis.referencing.ObjectDomain;
 
-// Specific to the geoapi-4.0 branch:
-import org.opengis.referencing.crs.GeodeticCRS;
-
 
 /**
  * Helper methods for building Coordinate Reference Systems and related objects.
@@ -469,7 +466,7 @@
      * @return the projected CRS.
      * @throws FactoryException if an error occurred while building the projected CRS.
      */
-    public ProjectedCRS createProjectedCRS(final GeodeticCRS baseCRS, CartesianCS derivedCS) throws FactoryException {
+    public ProjectedCRS createProjectedCRS(final GeographicCRS baseCRS, CartesianCS derivedCS) throws FactoryException {
         ensureConversionMethodSet();
         onCreate(false);
         try {
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 24d4cb6..b048979 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
@@ -22,8 +22,9 @@
 import org.apache.sis.referencing.NamedIdentifier;
 import org.apache.sis.util.resources.Vocabulary;
 
-// Specific to the geoapi-4.0 branch:
-import org.opengis.metadata.Identifier;
+// Specific to the main and geoapi-3.1 branches:
+import org.opengis.referencing.ReferenceIdentifier;
+import org.opengis.util.InternationalString;
 
 
 /**
@@ -45,7 +46,7 @@
      * the value of the {@code <gml:name>} element anyway at XML unmarshalling time.
      * But not all XML documents are valid, so the {@code <gml:name>} may be missing.
      */
-    public static final Identifier UNNAMED = new NamedIdentifier(null, Vocabulary.format(Vocabulary.Keys.Unnamed));
+    public static final ReferenceIdentifier UNNAMED = new NamedIdentifier(null, Vocabulary.format(Vocabulary.Keys.Unnamed));
 
     /**
      * The unique instance.
@@ -70,7 +71,16 @@
      * Returns the localized "unnamed" name because this property is mandatory.
      */
     @Override
-    public Identifier getName() {
+    public ReferenceIdentifier getName() {
         return UNNAMED;
     }
+
+    /**
+     * For avoiding ambiguity.
+     */
+    @Override
+    @Deprecated
+    public InternationalString getScope() {
+        return null;
+    }
 }
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 3179cdf..5a4af59 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
@@ -306,14 +306,15 @@
      * @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.
          * More generally, derived CRS are always derived from a base, which
          * is often (but not necessarily) geographic.
          */
-        while (crs instanceof DerivedCRS) {
-            crs = ((DerivedCRS) crs).getBaseCRS();
+        while (crs instanceof GeneralDerivedCRS) {
+            crs = ((GeneralDerivedCRS) crs).getBaseCRS();
         }
         if (crs instanceof GeodeticCRS) {
             /*
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 35cf70a..b9ea8c0 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
@@ -238,6 +238,7 @@
          * because they are implied by the `GeodeticCRS` type in the `subtypes` array, so they do not
          * need to be repeated there.
          */
+        addType(org.opengis.referencing.crs.GeocentricCRS.class,  GeodeticCRS, GeodCRS, GeocCS);
         addType(org.opengis.referencing.crs.GeographicCRS.class,  GeodeticCRS, GeodCRS, GeogCS);
         String[][] subtypes;
         subtypes = new String[][] {
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 72f4144..9a08fba 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
@@ -46,6 +46,9 @@
 import org.apache.sis.xml.bind.gco.PropertyType;
 import org.apache.sis.pending.jdk.JDK19;
 
+// Specific to the main and geoapi-3.1 branches:
+import org.opengis.referencing.ReferenceIdentifier;
+
 
 /**
  * JAXB adapter mapping implementing class to the GeoAPI interface. See
@@ -283,7 +286,7 @@
         final Map<String,Object> merged = new HashMap<>(expected);
         merged.putAll(actual);                                      // May overwrite predefined properties.
         mergeArrays(GeneralParameterDescriptor.ALIAS_KEY,       GenericName.class, provided.getAlias(), merged, complete.getName());
-        mergeArrays(GeneralParameterDescriptor.IDENTIFIERS_KEY, Identifier.class,  provided.getIdentifiers(), merged, null);
+        mergeArrays(GeneralParameterDescriptor.IDENTIFIERS_KEY, ReferenceIdentifier.class, provided.getIdentifiers(), merged, null);
         if (isGroup) {
             final List<GeneralParameterDescriptor> descriptors = ((ParameterDescriptorGroup) provided).descriptors();
             return merge(DefaultParameterValueGroup.class, merged, merged, minimumOccurs, maximumOccurs,
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/xml/bind/referencing/CD_ImageDatum.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/xml/bind/referencing/CD_ImageDatum.java
index 2e1c5d7..b3eaa6b 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/xml/bind/referencing/CD_ImageDatum.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/xml/bind/referencing/CD_ImageDatum.java
@@ -20,6 +20,9 @@
 import org.apache.sis.xml.bind.gco.PropertyType;
 import org.apache.sis.referencing.datum.DefaultImageDatum;
 
+// Specific to the main and geoapi-3.1 branches:
+import org.opengis.referencing.datum.ImageDatum;
+
 
 /**
  * JAXB adapter mapping implementing class to the GeoAPI interface. See
@@ -28,7 +31,8 @@
  * @author  Cédric Briançon (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  */
-public final class CD_ImageDatum extends PropertyType<CD_ImageDatum, DefaultImageDatum> {
+@SuppressWarnings("deprecation")
+public final class CD_ImageDatum extends PropertyType<CD_ImageDatum, ImageDatum> {
     /**
      * Empty constructor for JAXB only.
      */
@@ -43,14 +47,14 @@
      * @return {@code ImageDatum.class}
      */
     @Override
-    protected Class<DefaultImageDatum> getBoundType() {
-        return DefaultImageDatum.class;
+    protected Class<ImageDatum> getBoundType() {
+        return ImageDatum.class;
     }
 
     /**
      * Constructor for the {@link #wrap} method only.
      */
-    private CD_ImageDatum(final DefaultImageDatum datum) {
+    private CD_ImageDatum(final ImageDatum datum) {
         super(datum);
     }
 
@@ -62,7 +66,7 @@
      * @return a {@code PropertyType} wrapping the given the element.
      */
     @Override
-    protected CD_ImageDatum wrap(final DefaultImageDatum datum) {
+    protected CD_ImageDatum wrap(final ImageDatum datum) {
         return new CD_ImageDatum(datum);
     }
 
@@ -75,7 +79,7 @@
      */
     @XmlElement(name = "ImageDatum")
     public DefaultImageDatum getElement() {
-        return metadata;
+        return DefaultImageDatum.castOrCopy(metadata);
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/xml/bind/referencing/CD_PixelInCell.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/xml/bind/referencing/CD_PixelInCell.java
new file mode 100644
index 0000000..6a1f018
--- /dev/null
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/xml/bind/referencing/CD_PixelInCell.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.xml.bind.referencing;
+
+import org.opengis.referencing.datum.PixelInCell;
+import org.apache.sis.xml.bind.gml.CodeListAdapter;
+
+
+/**
+ * JAXB adapter for (un)marshalling of GeoAPI code list.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ */
+public final class CD_PixelInCell extends CodeListAdapter<PixelInCell> {
+    /**
+     * Empty constructor for JAXB only.
+     */
+    public CD_PixelInCell() {
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @return {@code PixelInCell.class}
+     */
+    @Override
+    protected Class<PixelInCell> getCodeListClass() {
+        return PixelInCell.class;
+    }
+}
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
new file mode 100644
index 0000000..a6eeffe
--- /dev/null
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/xml/bind/referencing/CD_VerticalDatumType.java
@@ -0,0 +1,45 @@
+/*
+ * 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.referencing;
+
+import org.opengis.referencing.datum.VerticalDatumType;
+import org.apache.sis.xml.bind.gml.CodeListAdapter;
+
+
+/**
+ * JAXB adapter for (un)marshalling of GeoAPI code list.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ */
+@SuppressWarnings("deprecation")
+public final class CD_VerticalDatumType extends CodeListAdapter<VerticalDatumType> {
+    /**
+     * Empty constructor for JAXB only.
+     */
+    public CD_VerticalDatumType() {
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @return {@code VerticalDatumType.class}
+     */
+    @Override
+    protected Class<VerticalDatumType> getCodeListClass() {
+        return VerticalDatumType.class;
+    }
+}
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 488042c..c1022ef 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
@@ -20,6 +20,9 @@
 import org.apache.sis.referencing.cs.DefaultUserDefinedCS;
 import org.apache.sis.xml.bind.gco.PropertyType;
 
+// Specific to the main and geoapi-3.1 branches:
+import org.opengis.referencing.cs.UserDefinedCS;
+
 
 /**
  * JAXB adapter mapping implementing class to the GeoAPI interface. See
@@ -27,7 +30,8 @@
  *
  * @author  Martin Desruisseaux (Geomatys)
  */
-public final class CS_UserDefinedCS extends PropertyType<CS_UserDefinedCS, DefaultUserDefinedCS> {
+@SuppressWarnings("deprecation")
+public final class CS_UserDefinedCS extends PropertyType<CS_UserDefinedCS, UserDefinedCS> {
     /**
      * Empty constructor for JAXB only.
      */
@@ -42,14 +46,14 @@
      * @return {@code UserDefinedCS.class}
      */
     @Override
-    protected Class<DefaultUserDefinedCS> getBoundType() {
-        return DefaultUserDefinedCS.class;
+    protected Class<UserDefinedCS> getBoundType() {
+        return UserDefinedCS.class;
     }
 
     /**
      * Constructor for the {@link #wrap} method only.
      */
-    private CS_UserDefinedCS(final DefaultUserDefinedCS cs) {
+    private CS_UserDefinedCS(final UserDefinedCS cs) {
         super(cs);
     }
 
@@ -61,7 +65,7 @@
      * @return a {@code PropertyType} wrapping the given the element.
      */
     @Override
-    protected CS_UserDefinedCS wrap(final DefaultUserDefinedCS cs) {
+    protected CS_UserDefinedCS wrap(final UserDefinedCS cs) {
         return new CS_UserDefinedCS(cs);
     }
 
@@ -74,7 +78,7 @@
      */
     @XmlElement(name = "UserDefinedCS")
     public DefaultUserDefinedCS getElement() {
-        return metadata;
+        return DefaultUserDefinedCS.castOrCopy(metadata);
     }
 
     /**
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 0defa6f..f7cb318 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
@@ -28,6 +28,9 @@
 import org.apache.sis.metadata.iso.citation.Citations;
 import org.apache.sis.referencing.NamedIdentifier;
 
+// Specific to the main and geoapi-3.1 branches:
+import org.opengis.referencing.ReferenceIdentifier;
+
 
 /**
  * The {@code gml:CodeType}, which is made of a code space and a code value.
@@ -91,7 +94,7 @@
      *
      * @return the identifier, or {@code null} if none.
      */
-    public Identifier getIdentifier() {
+    public ReferenceIdentifier getIdentifier() {
         String c = code;
         if (c == null) {
             return null;
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/xml/bind/referencing/SC_GeodeticCRS.md b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/xml/bind/referencing/SC_GeodeticCRS.md
deleted file mode 100644
index 7cead9d..0000000
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/xml/bind/referencing/SC_GeodeticCRS.md
+++ /dev/null
@@ -1,2 +0,0 @@
-The `SC_GeodeticCRS` adapter is defined in the `org.apache.sis.referencing.crs`
-package because it needs access to `DefaultGeodeticCRS` package-private methods.
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/xml/bind/referencing/SC_GeographicCRS.md b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/xml/bind/referencing/SC_GeographicCRS.md
new file mode 100644
index 0000000..961fa20
--- /dev/null
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/xml/bind/referencing/SC_GeographicCRS.md
@@ -0,0 +1,4 @@
+The `SC_GeographicCRS` adapter is defined in the `org.apache.sis.referencing.crs`
+package because it needs access to `DefaultGeodeticCRS` package-private methods.
+Note also that `GeographicCRS` does not exist in GML, so this is a special case
+anyway.
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 4610b54..6356d05 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
@@ -45,6 +45,9 @@
 import org.apache.sis.measure.Units;
 import static org.apache.sis.util.privy.Constants.SECONDS_PER_DAY;
 
+// Specific to the main and geoapi-3.1 branches:
+import org.apache.sis.util.privy.TemporalDate;
+
 // Test dependencies
 import org.junit.jupiter.api.Test;
 import static org.junit.jupiter.api.Assertions.*;
@@ -1065,7 +1068,7 @@
         final TemporalDatum timeDatum = timeCRS.getDatum();
         assertNameAndIdentifierEqual("Time", 0, timeCRS);
         assertNameAndIdentifierEqual("Modified Julian", 0, timeDatum);
-        assertEquals(Instant.ofEpochSecond(-40587L * SECONDS_PER_DAY), timeDatum.getOrigin(), "epoch");
+        assertEquals(Instant.ofEpochSecond(-40587L * SECONDS_PER_DAY), TemporalDate.toTemporal(timeDatum.getOrigin()), "epoch");
 
         // No more CRS.
         assertFalse(components.hasNext());
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 e07cb50..121ec08 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
@@ -37,14 +37,14 @@
 import org.apache.sis.test.TestCase;
 import org.apache.sis.test.TestUtilities;
 
+// 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;
 
-// Specific to the geoapi-4.0 branch:
-import org.opengis.metadata.Identifier;
-
 
 /**
  * Tests the static methods in the {@link Parameters} class.
@@ -110,10 +110,10 @@
     {
         assertEquals(valueDomain, Parameters.getValueDomain(descriptor));
         assertEquals(valueDomain, Parameters.getValueDomain(new ParameterDescriptor<T>() {
-            @Override public Identifier                    getName()          {return descriptor.getName();}
+            @Override public ReferenceIdentifier           getName()          {return descriptor.getName();}
             @Override public Collection<GenericName>       getAlias()         {return descriptor.getAlias();}
-            @Override public Set<Identifier>               getIdentifiers()   {return descriptor.getIdentifiers();}
-            @Override public Optional<InternationalString> getRemarks()       {return descriptor.getRemarks();}
+            @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();}
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/AbstractIdentifiedObjectTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/AbstractIdentifiedObjectTest.java
index 4479834..143f1f0 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/AbstractIdentifiedObjectTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/AbstractIdentifiedObjectTest.java
@@ -37,8 +37,8 @@
 import static org.apache.sis.test.TestUtilities.getSingleton;
 import static org.apache.sis.referencing.Assertions.assertRemarksEquals;
 
-// Specific to the geoapi-4.0 branch:
-import org.opengis.metadata.Identifier;
+// Specific to the main and geoapi-3.1 branches:
+import org.opengis.referencing.ReferenceIdentifier;
 
 
 /**
@@ -59,10 +59,10 @@
      *
      * @param  identifiers  the value for the {@code "identifiers"} property.
      */
-    private static Map<String,Object> properties(final Set<Identifier> identifiers) {
+    private static Map<String,Object> properties(final Set<ReferenceIdentifier> identifiers) {
         final var properties = new HashMap<String,Object>(8);
         assertNull(properties.put("name",       "GRS 1980"));
-        assertNull(properties.put("identifiers", identifiers.toArray(Identifier[]::new)));
+        assertNull(properties.put("identifiers", identifiers.toArray(ReferenceIdentifier[]::new)));
         assertNull(properties.put("codespace",  "EPSG"));
         assertNull(properties.put("version",    "8.3"));
         assertNull(properties.put("alias",      "International 1979"));
@@ -79,8 +79,8 @@
      * @param  gmlID        the expected value of {@link AbstractIdentifiedObject#getID()}.
      * @return the value of {@link AbstractIdentifiedObject#getIdentifier()}.
      */
-    private static Identifier validate(final AbstractIdentifiedObject object,
-            final Set<Identifier> identifiers, final String gmlID)
+    private static ReferenceIdentifier validate(final AbstractIdentifiedObject object,
+            final Set<ReferenceIdentifier> identifiers, final String gmlID)
     {
         Validators.validate(object);
         final var name = object.getName();
@@ -131,7 +131,7 @@
      */
     @Test
     public void testWithoutIdentifier() {
-        final var identifiers = Set.<Identifier>of();
+        final var identifiers = Set.<ReferenceIdentifier>of();
         final var object      = new AbstractIdentifiedObject(properties(identifiers));
         final var gmlId       = validate(object, identifiers, "GRS1980");
         assertNull(gmlId);
@@ -150,7 +150,7 @@
     @Test
     public void testWithSingleIdentifier() {
         final var identifier  = new ImmutableIdentifier(null, "EPSG", "7019");
-        final var identifiers = Set.<Identifier>of(identifier);
+        final var identifiers = Set.<ReferenceIdentifier>of(identifier);
         final var object      = new AbstractIdentifiedObject(properties(identifiers));
         final var gmlId       = validate(object, identifiers, "epsg-7019");
         assertNotNull(        gmlId);
@@ -165,7 +165,7 @@
      */
     @Test
     public void testWithManyIdentifiers() {
-        final var identifiers = new LinkedHashSet<Identifier>(4);
+        final var identifiers = new LinkedHashSet<ReferenceIdentifier>(4);
         assertTrue(identifiers.add(new NamedIdentifier(EPSG, "7019")));
         assertTrue(identifiers.add(new NamedIdentifier(EPSG, "IgnoreMe")));
         final var object = new AbstractIdentifiedObject(properties(identifiers));
@@ -183,7 +183,7 @@
     @Test
     public void testAsSubtype() {
         final var identifier  = new NamedIdentifier(EPSG, "7019");
-        final var identifiers = Set.<Identifier>of(identifier);
+        final var identifiers = Set.<ReferenceIdentifier>of(identifier);
         final var object      = new AbstractDatum(properties(identifiers));
         final var gmlId       = validate(object, identifiers, "epsg-datum-7019");
         assertNotNull(        gmlId);
@@ -225,7 +225,7 @@
      */
     @Test
     public void testSerialization() {
-        final var identifiers = Set.<Identifier>of();
+        final var identifiers = Set.<ReferenceIdentifier>of();
         final var object = new AbstractIdentifiedObject(properties(identifiers));
         final var actual = assertSerializedEquals(object);
         assertNotSame(object, actual);
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 d571c2d..360d535 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
@@ -150,9 +150,8 @@
      * @param locale    the locale to test, or {@code null}.
      */
     public static void assertRemarksEquals(final String expected, final IdentifiedObject object, final Locale locale) {
-        var remarks = object.getRemarks()
-                .map((locale != null) ? (i18n) -> i18n.toString(locale) : InternationalString::toString)
-                .orElse(null);
+        InternationalString i18n = object.getRemarks();
+        String remarks = (i18n == null) ? null : (locale != null) ? i18n.toString(locale) : i18n.toString();
         assertEquals(expected, remarks, "remarks");
     }
 
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 712918b..d2a66ce 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
@@ -54,6 +54,7 @@
 // Specific to the geoapi-3.1 and geoapi-4.0 branches:
 import org.opengis.referencing.datum.RealizationMethod;
 import static org.opengis.test.Assertions.assertAxisDirectionsEqual;
+import org.apache.sis.util.privy.TemporalDate;
 
 
 /**
@@ -254,7 +255,7 @@
      */
     @Test
     public void testTemporal() {
-        final var julianEpoch = (Instant) CommonCRS.Temporal.JULIAN.datum().getOrigin();
+        final var julianEpoch = TemporalDate.toInstant(CommonCRS.Temporal.JULIAN.datum().getOrigin());
         final double SECONDS_PER_DAY = Constants.SECONDS_PER_DAY;
         final double julianEpochSecond = julianEpoch.getEpochSecond() / SECONDS_PER_DAY;
         assertTrue(julianEpochSecond < 0);
@@ -284,12 +285,12 @@
             final String        name   = e.name();
             final TemporalDatum datum  = e.datum();
             final TemporalCRS   crs    = e.crs();
-            final Instant       origin = assertInstanceOf(Instant.class, datum.getOrigin());
+            final Date          origin = datum.getOrigin();
             Validators.validate(crs);
             assertSame(datum, e.datum(), name);             // Datum before CRS creation.
             assertSame(crs.getDatum(), e.datum(), name);    // Datum after CRS creation.
-            assertEquals(epoch, dateFormat.format(Date.from(origin)), name);
-            assertEquals(days, origin.getEpochSecond() / SECONDS_PER_DAY - julianEpochSecond, name);
+            assertEquals(epoch, dateFormat.format(origin), name);
+            assertEquals(days, origin.getTime() / (1000*SECONDS_PER_DAY) - julianEpochSecond, name);
             switch (e) {
                 case JAVA: {
                     assertNameContains(datum, "Unix/POSIX");
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 ec4649d..a1cbedb 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
@@ -22,6 +22,7 @@
 import org.opengis.referencing.cs.AffineCS;
 import org.opengis.referencing.cs.CartesianCS;
 import org.opengis.referencing.cs.CoordinateSystem;
+import org.opengis.referencing.datum.PixelInCell;
 import org.apache.sis.referencing.cs.DefaultAffineCS;
 import org.apache.sis.referencing.datum.DefaultImageDatum;
 import org.apache.sis.io.wkt.Convention;
@@ -60,7 +61,7 @@
      */
     private static DefaultImageCRS create(final boolean cartesian) {
         return new DefaultImageCRS(Map.of(DefaultImageCRS.NAME_KEY, "An image CRS"),
-                new DefaultImageDatum(Map.of(DefaultImageDatum.NAME_KEY, "C1"), "cell center"),
+                new DefaultImageDatum(Map.of(DefaultImageDatum.NAME_KEY, "C1"), PixelInCell.CELL_CENTER),
                         cartesian ? HardCodedCS.GRID : new DefaultAffineCS(
                                 Map.of(DefaultAffineCS.NAME_KEY, "Grid"),
                                 HardCodedAxes.COLUMN, HardCodedAxes.ROW));
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 6f5d812..3a7a54c 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
@@ -18,6 +18,7 @@
 
 import java.util.Map;
 import java.util.HashMap;
+import org.opengis.referencing.datum.PixelInCell;
 import static org.opengis.referencing.IdentifiedObject.*;
 import org.apache.sis.referencing.NamedIdentifier;
 import org.apache.sis.metadata.iso.extent.Extents;
@@ -320,7 +321,7 @@
      * this CS cannot be reprojected to a geographic coordinate reference system for example).
      *
      * <p>The {@code pixelInCell} attribute of the associated {@code ImageDatum}
-     * is set to {@link org.apache.sis.coverage.grid.PixelInCell#CELL_CENTER}.</p>
+     * is set to {@link PixelInCell#CELL_CENTER}.</p>
      */
     @SuppressWarnings("removal")
     public static final DefaultImageCRS IMAGE = new DefaultImageCRS(
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 ceae5c8..4b8a95d 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
@@ -34,9 +34,6 @@
 import static org.apache.sis.test.Assertions.assertEqualsIgnoreMetadata;
 import static org.apache.sis.referencing.Assertions.assertAxisEquals;
 
-// Specific to the geoapi-4.0 branch:
-import org.apache.sis.referencing.internal.Legacy;
-
 
 /**
  * Tests the {@link Normalizer} class.
@@ -116,14 +113,15 @@
      * with axes of legacy (WKT 1) axes.
      */
     @Test
+    @SuppressWarnings("deprecation")
     public void testSortWKT1() {
         assertOrdered(new AxisDirection[] {
-            Legacy.OTHER,
+            AxisDirection.OTHER,
             AxisDirection.EAST,
             AxisDirection.NORTH
         }, new AxisDirection[] {
             AxisDirection.NORTH,
-            Legacy.OTHER,
+            AxisDirection.OTHER,
             AxisDirection.EAST
         });
     }
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 443246a..58b4981 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,6 +34,9 @@
 import static org.apache.sis.test.TestUtilities.getSingleton;
 import static org.apache.sis.test.TestUtilities.getScope;
 
+// 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.*;
@@ -88,7 +91,7 @@
      */
     @Test
     public void testConsistency() {
-        assertEquals(HardCodedDatum.MODIFIED_JULIAN.getOrigin(), ORIGIN.toInstant());
+        assertEquals(TemporalDate.toTemporal(HardCodedDatum.MODIFIED_JULIAN.getOrigin()), ORIGIN.toInstant());
     }
 
     /**
@@ -131,6 +134,6 @@
         assertEquals("Modified Julian", datum.getName().getCode());
         assertRemarksEquals("Time measured as days since November 17, 1858 at 00:00 UTC.", datum, null);
         assertEquals("History.", getScope(datum));
-        assertEquals(ORIGIN, datum.getOrigin());
+        assertEquals(ORIGIN, TemporalDate.toTemporal(datum.getOrigin()));
     }
 }
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 3edb04e..dedd076 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
@@ -19,6 +19,7 @@
 import java.util.Map;
 import java.util.HashMap;
 import java.time.Instant;
+import org.opengis.referencing.datum.PixelInCell;
 import org.apache.sis.referencing.NamedIdentifier;
 import org.apache.sis.referencing.internal.VerticalDatumTypes;
 import org.apache.sis.measure.Units;
@@ -151,12 +152,12 @@
             properties("Day of year", null, null));
 
     /**
-     * Image with {@link org.apache.sis.coverage.grid.PixelInCell#CELL_CENTER}.
+     * Image with {@link PixelInCell#CELL_CENTER}.
      */
     @SuppressWarnings("removal")
     public static final DefaultImageDatum IMAGE = new DefaultImageDatum(
             properties("Image", null, null),
-            "cell center");
+            PixelInCell.CELL_CENTER);
 
     /**
      * An engineering datum for unknown coordinate reference system. Such CRS are usually
@@ -186,8 +187,9 @@
     /**
      * Returns the scope of the given object.
      */
+    @SuppressWarnings("deprecation")
     private static CharSequence getScope(final AbstractDatum object) {
-        return object.getDomains().iterator().next().getScope();
+        return object.getScope();
     }
 
     /**
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 f176248..daa8d5f 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,6 +127,7 @@
      * @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/sql/epsg/README.md b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/sql/epsg/README.md
index 42f030d..6a90fc4 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-2.0-SNAPSHOT.jar:$CLASSPATH
+export CLASSPATH=$PWD/target/binaries/sis-referencing-1.x-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
index 4942e3c..d17bfae 100644
--- 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
@@ -33,6 +33,8 @@
  *
  * @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 {
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
index 05ff9a6..a9c8a40 100644
--- 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
@@ -34,6 +34,8 @@
  * 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 {
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 28f4e0c..a9be269 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
@@ -24,6 +24,9 @@
 import static org.junit.jupiter.api.Assertions.*;
 import org.apache.sis.test.TestCase;
 
+// 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;
 
@@ -63,13 +66,13 @@
     }
 
     /**
-     * Tests the {@link VerticalDatumTypes#toLegacy(RealizationMethod)} method.
+     * Tests the {@link VerticalDatumTypes#toLegacy(VerticalDatumType)} method.
      */
     @Test
     public void testToLegacy() {
-        assertEquals(2002, VerticalDatumTypes.toLegacy(VerticalDatumTypes.ellipsoidal()));
-        assertEquals(2005, VerticalDatumTypes.toLegacy(RealizationMethod .GEOID));
-        assertEquals(2006, VerticalDatumTypes.toLegacy(RealizationMethod .TIDAL));
+        assertEquals(2002, VerticalDatumTypes.toLegacy(VerticalDatumType.valueOf("ELLIPSOIDAL")));
+        assertEquals(2005, VerticalDatumTypes.toLegacy(VerticalDatumType.GEOIDAL));
+        assertEquals(2006, VerticalDatumTypes.toLegacy(VerticalDatumType.DEPTH));
     }
 
     /**
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 649738e..6030884 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
@@ -20,7 +20,6 @@
 import java.util.Map;
 import java.util.HashMap;
 import java.text.ParseException;
-import java.time.temporal.ChronoField;
 import org.opengis.util.FactoryException;
 import org.opengis.parameter.ParameterValueGroup;
 import org.opengis.referencing.cs.AxisDirection;
@@ -644,7 +643,7 @@
         tolerance = 2E-12;
         verifyTransform(new double[] {
             // December 31, 1899 at 12:00 UTC in seconds.
-            CommonCRS.Temporal.DUBLIN_JULIAN.datum().getOrigin().getLong(ChronoField.INSTANT_SECONDS)
+            CommonCRS.Temporal.DUBLIN_JULIAN.datum().getOrigin().getTime() / 1000
         }, new double[] {
             15019.5
         });
@@ -958,7 +957,7 @@
 
         tolerance = 2E-12;
         verifyTransform(new double[] {
-            -5, -8, CommonCRS.Temporal.DUBLIN_JULIAN.datum().getOrigin().getLong(ChronoField.INSTANT_SECONDS)
+            -5, -8, CommonCRS.Temporal.DUBLIN_JULIAN.datum().getOrigin().getTime() / 1000
         }, new double[] {
             -5, -8, 0, 15019.5              // Same value as in testTemporalConversion().
         });
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 ebd448c..a7d05b2 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
@@ -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");
-        assertTrue  (c.getOperationVersion().isEmpty(), "operationVersion");
+        assertNull(c.getOperationVersion(), "operationVersion");
 
         final GeographicBoundingBox e = getDomainOfValidity(c);
         assertEquals(+180, e.getEastBoundLongitude(), "eastBoundLongitude");
@@ -221,7 +221,7 @@
         assertEquals("NTF (Paris) to NTF (1)", c.getName().getCode(), "name");
         assertEquals("1763", getSingleton(c.getIdentifiers()).getCode(), "identifier");
         assertEquals("Change of prime meridian.", getScope(c), "scope");
-        assertEquals("IGN-Fra", c.getOperationVersion().get(), "operationVersion");
+        assertEquals("IGN-Fra", c.getOperationVersion(), "operationVersion");
 
         final OperationMethod method = c.getMethod();
         assertNotNull(method, "method");
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 8e12595..b926d08 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,9 +32,6 @@
 import org.apache.sis.referencing.cs.HardCodedCS;
 import org.apache.sis.test.TestCase;
 
-// Specific to the geoapi-4.0 branch:
-import org.apache.sis.referencing.internal.Legacy;
-
 
 /**
  * Tests the {@link AxisDirections} class.
@@ -203,15 +200,6 @@
     }
 
     /**
-     * Tests {@link AxisDirections#isLegacyOther(AxisDirection)}.
-     */
-    @Test
-    public void testIsLegacyOther() {
-        assertFalse(AxisDirections.isLegacyOther(NORTH));
-        assertTrue (AxisDirections.isLegacyOther(Legacy.OTHER));
-    }
-
-    /**
      * Tests {@link AxisDirections#isSpatialOrUserDefined(AxisDirection, boolean)} and
      * {@link AxisDirections#isGrid(AxisDirection)}.
      */
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 197a69f..1327cf3 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
@@ -332,7 +332,7 @@
             reopenTag("tr");
             println("td", escape(getFirstEpsgCode(param.getIdentifiers())));
             writeName(param);
-            String remarks = toLocalizedString(param.getRemarks().orElse(null));
+            String remarks = toLocalizedString(param.getRemarks());
             if (remarks != null) {
                 Integer index = footnotes.putIfAbsent(remarks, footnotes.size() + 1);
                 if (index == null) {
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
index 219b9ef..feab090 100644
--- 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
@@ -538,10 +538,10 @@
             row.isDeprecated = dep.isDeprecated();
             if (row.isDeprecated) {
                 String replacedBy = null;
-                InternationalString i18n = object.getRemarks().orElse(null);
+                InternationalString i18n = object.getRemarks();
                 for (final Identifier id : object.getIdentifiers()) {
                     if (id instanceof Deprecable did && did.isDeprecated()) {
-                        i18n = did.getRemarks().orElse(null);
+                        i18n = did.getRemarks();
                         if (id instanceof DeprecatedCode dc) {
                             replacedBy = dc.replacedBy;
                         }
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 7ab3a98..2d467cb 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
@@ -29,8 +29,8 @@
 import org.apache.sis.test.TestCase;
 import org.apache.sis.referencing.factory.TestFactorySource;
 
-// Specific to the geoapi-4.0 branch:
-import org.opengis.referencing.crs.ProjectedCRS;
+// Specific to the main and geoapi-3.1 branches:
+import org.opengis.referencing.crs.GeodeticCRS;
 
 
 /**
@@ -67,10 +67,10 @@
     public void testDerivedCRS() throws FactoryException {
         assertTrue(TestFactorySource.getSharedFactory() != null);
         CoordinateReferenceSystem crs = CRS.forCode("EPSG:5820");
-        assertInstanceOf(DerivedCRS  .class, crs);
-        assertInstanceOf(ProjectedCRS.class, crs);
-        assertInstanceOf(CartesianCS .class, crs.getCoordinateSystem());
-        assertInstanceOf(CartesianCS .class, ((DerivedCRS) crs).getBaseCRS().getCoordinateSystem());
+        assertInstanceOf(DerivedCRS .class, crs);
+        assertInstanceOf(GeodeticCRS.class, crs);
+        assertInstanceOf(CartesianCS.class, crs.getCoordinateSystem());
+        assertInstanceOf(CartesianCS.class, ((DerivedCRS) 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
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 6c6a37c..8bd6cf2 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
@@ -127,7 +127,7 @@
                     new DefaultTelephone("+33 (0)2 xx.xx.xx.x4", TelephoneType.FACSIMILE)
             ));
             final var address = new DefaultAddress();
-            address.setDeliveryPoints(Set.of(new SimpleInternationalString("Brest institute")));
+            address.setDeliveryPoints(Set.of("Brest institute"));
             address.setCity(new SimpleInternationalString("Plouzane"));
             address.setPostalCode("29280");
             address.setCountry(country);
@@ -156,7 +156,7 @@
                         new DefaultTelephone("+33 (0)4 xx.xx.xx.x8", TelephoneType.FACSIMILE)
                 ));
                 final var address = new DefaultAddress();
-                address.setDeliveryPoints(Set.of(new SimpleInternationalString("Oceanology institute")));
+                address.setDeliveryPoints(Set.of("Oceanology institute"));
                 address.setCity(new SimpleInternationalString("Marseille"));
                 address.setPostalCode("13288");
                 address.setCountry(country);
@@ -171,7 +171,7 @@
                     TopicCategory.OCEANS);                                      // Topic category
             {
                 @SuppressWarnings("deprecation")
-                final var custodian = new DefaultResponsibleParty(author);
+                final var custodian = new DefaultResponsibleParty((DefaultResponsibility) author);
                 custodian.setRole(Role.CUSTODIAN);
                 identification.setPointOfContacts(Set.of(custodian));
             }
@@ -294,7 +294,7 @@
                     Datatype.CODE_LIST,                                             // Data type
                     "SeaDataNet",                                                   // Parent entity
                     "For testing only",                                             // Rule
-                    NilReason.MISSING.createNilObject(Responsibility.class))));     // Source
+                    NilReason.MISSING.createNilObject(ResponsibleParty.class))));   // Source
             metadata.setMetadataExtensionInfo(Set.of(extensionInfo));
         }
         /*
@@ -302,7 +302,7 @@
          */
         {
             @SuppressWarnings("deprecation")
-            final var distributor = new DefaultResponsibleParty(author);
+            final var distributor = new DefaultResponsibleParty((DefaultResponsibility) author);
             final var distributionInfo = new DefaultDistribution();
             distributor.setRole(Role.DISTRIBUTOR);
             distributionInfo.setDistributors(Set.of(new DefaultDistributor(distributor)));
@@ -322,7 +322,7 @@
             onlines.setProtocol("ftp");
             transfer.setOnLines(Set.of(onlines));
             distributionInfo.setTransferOptions(Set.of(transfer));
-            metadata.setDistributionInfo(Set.of(distributionInfo));
+            metadata.setDistributionInfo(distributionInfo);
         }
         return metadata;
     }
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/xml/bind/referencing/CC_GeneralOperationParameterTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/xml/bind/referencing/CC_GeneralOperationParameterTest.java
index 9e45adb..11d9c34 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/xml/bind/referencing/CC_GeneralOperationParameterTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/xml/bind/referencing/CC_GeneralOperationParameterTest.java
@@ -72,7 +72,7 @@
          * illegal value) for this test.
          */
         assertEquals(name, p.getName().getCode());
-        assertEquals(remarks, (remarks == null) ? null : p.getRemarks().orElseThrow().toString());
+        assertEquals(remarks, (remarks == null) ? null : p.getRemarks().toString());
         assertTrue(p.getDescription().isEmpty());
         assertNull(p.getValueClass());
         assertEquals(0, p.getMinimumOccurs());
@@ -143,7 +143,7 @@
         assertEquals (0,                  merged.getMinimumOccurs());  // From provided descriptor.
         assertEquals (1,                  merged.getMaximumOccurs());
         assertEquals (Integer.class,      merged.getValueClass());     // From complete descriptor.
-        assertTrue   (                    merged.getRemarks().isEmpty());
+        assertNull   (                    merged.getRemarks());
 
         complete = create("Test parameter", null, false, null);
         assertSame(complete, CC_GeneralOperationParameter.merge(provided, complete));
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 7dc1aec..669a1cc 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
@@ -219,7 +219,7 @@
         assertSame(expected.getMaximumValue(), actual.getMaximumValue());
         assertSame(expected.getDefaultValue(), actual.getDefaultValue());
         if (remarks != null) {
-            assertEquals(remarks, actual.getRemarks().get().toString());
+            assertEquals(remarks, actual.getRemarks().toString());
         } else {
             assertEquals(expected.getRemarks(), actual.getRemarks());
         }
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 f718b5c..977a5fb 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
@@ -166,7 +166,7 @@
      * An object very similar is used as the creator. The point of contact and the creator
      * are often identical except for their role attribute.
      */
-    private transient Responsibility pointOfContact;
+    private transient ResponsibleParty pointOfContact;
 
     /**
      * The vertical coordinate reference system to be given to the object created by {@link #addExtent()}.
@@ -431,7 +431,7 @@
      * @see AttributeNames#CONTRIBUTOR
      * @see AttributeNames#PUBLISHER
      */
-    private Responsibility createResponsibleParty(final Responsible keys, final boolean isPointOfContact) {
+    private ResponsibleParty createResponsibleParty(final Responsible keys, final boolean isPointOfContact) {
         String individualName   = stringValue(keys.NAME);
         String organisationName = stringValue(keys.INSTITUTION);
         final String email      = stringValue(keys.EMAIL);
@@ -463,7 +463,7 @@
          * Verify if we can share the existing `pointOfContact` instance. This is often the case in practice.
          * If we cannot share the whole existing instance, we usually can share parts of it like the address.
          */
-        Responsibility responsibility = pointOfContact;
+        ResponsibleParty responsibility = pointOfContact;
         Contact        contact        = null;
         Address        address        = null;
         OnlineResource resource       = null;
@@ -516,7 +516,10 @@
                 if (organisationName != null) party = new DefaultOrganisation(organisationName, null, (Individual) party, null);
                 if (party            == null) party = isOrganisation(keys) ? new DefaultOrganisation() : new DefaultIndividual();
                 if (contact          != null) party.setContactInfo(Set.of(contact));
-                responsibility = new DefaultResponsibility(role, null, party);
+                responsibility = new DefaultResponsibleParty(role);
+                if (party != null) {
+                    ((DefaultResponsibleParty) responsibility).setParties(Set.of(party));
+                }
             }
         }
         return responsibility;
@@ -567,7 +570,7 @@
          */
         for (final String path : searchPath) {
             decoder.setSearchPath(path);
-            final Responsibility party = createResponsibleParty(CREATOR, true);
+            final ResponsibleParty party = createResponsibleParty(CREATOR, true);
             if (party != pointOfContact) {
                 addPointOfContact(party, Scope.RESOURCE);
                 if (pointOfContact == null) {
@@ -588,11 +591,11 @@
         Set<InternationalString> publisher = null;
         for (final String path : searchPath) {
             decoder.setSearchPath(path);
-            final Responsibility contributor = createResponsibleParty(CONTRIBUTOR, false);
+            final ResponsibleParty contributor = createResponsibleParty(CONTRIBUTOR, false);
             if (contributor != pointOfContact) {
                 addCitedResponsibleParty(contributor, null);
             }
-            final Responsibility r = createResponsibleParty(PUBLISHER, false);
+            final ResponsibleParty r = createResponsibleParty(PUBLISHER, false);
             if (r != null) {
                 addDistributor(r);
                 for (final Party party : r.getParties()) {
@@ -622,7 +625,7 @@
             for (final String keyword : split(stringValue(ACCESS_CONSTRAINT))) {
                 addAccessConstraint(forCodeName(Restriction.class, keyword));
             }
-            addTopicCategory(forEnumName(TopicCategory.class, stringValue(TOPIC_CATEGORY)));
+            addTopicCategory(forCodeName(TopicCategory.class, stringValue(TOPIC_CATEGORY)));
             addSpatialRepresentation(forCodeName(SpatialRepresentationType.class, stringValue(DATA_TYPE)));
             if (!hasExtent) {
                 /*
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 06d5b32..0593a47 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
@@ -28,10 +28,6 @@
 import org.opengis.metadata.citation.OnlineResource;
 import org.apache.sis.xml.bind.Context;
 
-// Specific to the geoapi-4.0 branch:
-import org.apache.sis.util.SimpleInternationalString;
-import org.apache.sis.util.iso.Types;
-
 
 /**
  * A link to an external resource (Web page, digital photo, video clip, <i>etc</i>) with additional information.
@@ -133,7 +129,7 @@
      */
     private Link(final OnlineResource r, final Locale locale) {
         uri  = r.getLinkage();
-        text = Types.toString(r.getName(), locale);
+        text = r.getName();
     }
 
     /**
@@ -164,8 +160,8 @@
      * @return name of the online resource.
      */
     @Override
-    public InternationalString getName() {
-        return (text != null) ? new SimpleInternationalString(text) : null;
+    public String getName() {
+        return text;
     }
 
     /**
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 ffd54b8..439c0c7 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
@@ -50,6 +50,9 @@
 import org.apache.sis.referencing.CommonCRS;
 import org.apache.sis.util.iso.Types;
 
+// 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;
@@ -313,10 +316,10 @@
      * @return means of communication with person(s) and organisations(s) associated with the resource.
      */
     @Override
-    public Collection<Responsibility> getPointOfContacts() {
+    public Collection<ResponsibleParty> getPointOfContacts() {
         if (creator != null) {
             final Person p = new Person(creator);
-            return (author != null) ? UnmodifiableArrayList.wrap(new Responsibility[] {p, author})
+            return (author != null) ? UnmodifiableArrayList.wrap(new ResponsibleParty[] {p, author})
                                     : Collections.singletonList(author);
         }
         return (author != null) ? Collections.singletonList(author) : Collections.emptyList();
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 07c0985..e194656 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
@@ -28,12 +28,20 @@
 import org.opengis.metadata.citation.Role;
 import org.opengis.util.InternationalString;
 
+// Specific to the main and geoapi-3.1 branches:
+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,7 +64,7 @@
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  */
-public final class Person implements Responsibility, Party, Contact, Address {
+public final class Person implements ResponsibleParty, Party, Contact, Address {
     /**
      * Name of person or organization.
      *
@@ -171,6 +179,37 @@
     }
 
     /**
+     * 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.
+     *
+     * @return name of the organization, or {@code null} if none.
+     */
+    @Override
+    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 #name} field.
+     *
+     * @return name of the party, or {@code null} if none.
+     */
+    @Override
+    public String getIndividualName() {
+        return name;
+    }
+
+    /**
      * 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.
@@ -181,8 +220,28 @@
      * @see #getOnlineResources()
      */
     @Override
-    public Collection<? extends Contact> getContactInfo() {
-        return thisOrEmpty(email != null || link != null);
+    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();}
     }
 
 
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 570b35d..b71b7cc 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
@@ -29,8 +29,8 @@
 import org.apache.sis.metadata.iso.constraint.DefaultLegalConstraints;
 import static org.apache.sis.util.privy.Constants.MILLISECONDS_PER_DAY;
 
-// Specific to the geoapi-4.0 branch:
-import org.apache.sis.metadata.iso.citation.DefaultResponsibility;
+// Specific to the main and geoapi-3.1 branches:
+import org.apache.sis.metadata.iso.citation.DefaultResponsibleParty;
 
 
 /**
@@ -214,7 +214,8 @@
             buffer.setLength(i);
             // Same limitation as MetadataBuilder.party().
             final var party = new AbstractParty(buffer, null);
-            final var r = new DefaultResponsibility(Role.OWNER, null, party);
+            final var r = new DefaultResponsibleParty(Role.OWNER);
+            r.setParties(Collections.singleton(party));
             c.setCitedResponsibleParties(Collections.singleton(r));
         }
         constraints.getReferences().add(c);
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 336e8ca..ae68538 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
@@ -262,16 +262,16 @@
     /**
      * Part of the responsible party of the {@linkplain #citation}, or {@code null} if none.
      */
-    private DefaultResponsibility responsibility;
+    private DefaultResponsibleParty responsibility;
 
     /**
      * Creates the responsibility object if it does not already exists, then returns it.
      *
      * @return the responsibility party (never {@code null}).
      */
-    private DefaultResponsibility responsibility() {
+    private DefaultResponsibleParty responsibility() {
         if (responsibility == null) {
-            responsibility = new DefaultResponsibility();
+            responsibility = new DefaultResponsibleParty();
         }
         return responsibility;
     }
@@ -707,7 +707,7 @@
      */
     public final void newDistribution() {
         if (distribution != null) {
-            addIfNotPresent(metadata().getDistributionInfo(), distribution);
+            metadata().setDistributionInfo(distribution);
             distribution = null;
         }
     }
@@ -1301,10 +1301,9 @@
      * @param  page  the page, or {@code null} for no-operation.
      */
     public final void addPage(final CharSequence page) {
-        final InternationalString i18n = trim(page);
-        if (i18n != null) {
+        if (page != null) {
             final DefaultSeries series = series();
-            series.setPage(append(series.getPage(), i18n));
+            series.setPage(page.toString());
         }
     }
 
@@ -1384,7 +1383,8 @@
     public final void addOtherCitationDetails(final CharSequence details) {
         final InternationalString i18n = trim(details);
         if (i18n != null) {
-            addIfNotPresent(citation().getOtherCitationDetails(), i18n);
+            final DefaultCitation citation = citation();
+            citation.setOtherCitationDetails(append(citation.getOtherCitationDetails(), i18n));
         }
     }
 
@@ -1510,10 +1510,10 @@
      * @param  party  the individual or organization that is responsible, or {@code null} for no-operation.
      * @param  role   the role to set, or {@code null} for leaving it unchanged.
      */
-    public final void addCitedResponsibleParty(Responsibility party, final Role role) {
+    public final void addCitedResponsibleParty(ResponsibleParty party, final Role role) {
         if (party != null) {
             if (role != null && !role.equals(party.getRole())) {
-                party = new DefaultResponsibility(party);
+                party = new DefaultResponsibleParty(party);
                 ((DefaultResponsibility) party).setRole(role);
             }
             addIfNotPresent(citation().getCitedResponsibleParties(), party);
@@ -1533,7 +1533,7 @@
      * @param  contact  means of communication with party associated with the resource, or {@code null} for no-operation.
      * @param  scope    whether the contact applies to data, to metadata or to both.
      */
-    public final void addPointOfContact(final Responsibility contact, final Scope scope) {
+    public final void addPointOfContact(final ResponsibleParty contact, final Scope scope) {
         if (contact != null) {
             if (scope != Scope.RESOURCE) addIfNotPresent(metadata().getContacts(), contact);
             if (scope != Scope.METADATA) addIfNotPresent(identification().getPointOfContacts(), contact);
@@ -1550,7 +1550,7 @@
      *
      * @param  distributor  the distributor, or {@code null} for no-operation.
      */
-    public final void addDistributor(final Responsibility distributor) {
+    public final void addDistributor(final ResponsibleParty distributor) {
         if (distributor != null) {
             addIfNotPresent(distribution().getDistributors(), new DefaultDistributor(distributor));
         }
@@ -1567,9 +1567,11 @@
      * @param  credit  recognition of those who contributed to the resource, or {@code null} for no-operation.
      */
     public final void addCredits(final CharSequence credit) {
-        final InternationalString i18n = trim(credit);
-        if (i18n != null) {
-            addIfNotPresent(identification().getCredits(), i18n);
+        if (credit != null) {
+            final String c = CharSequences.trimWhitespaces(credit).toString();
+            if (!c.isEmpty()) {
+                addIfNotPresent(identification().getCredits(), c);
+            }
         }
     }
 
@@ -2840,7 +2842,7 @@
                 final var scope = new DefaultScope(level);
                 if (feature != null) {
                     final var sd = new DefaultScopeDescription();
-                    sd.getFeatures().add(feature);
+                    sd.getFeatures().add(new org.apache.sis.metadata.iso.maintenance.LegacyFeatureType(feature));
                     scope.getLevelDescription().add(sd);
                 }
             }
@@ -3176,7 +3178,7 @@
                 if (citation.getTitle() == null) {
                     citation.setTitle(c.getTitle());
                 }
-                for (Responsibility r : c.getCitedResponsibleParties()) {
+                for (ResponsibleParty r : c.getCitedResponsibleParties()) {
                     addIfNotPresent(citation.getCitedResponsibleParties(), r);
                 }
                 for (OnlineResource r : c.getOnlineResources()) {
@@ -3219,9 +3221,10 @@
         for (AcquisitionInformation info : component.getAcquisitionInformation()) {
             addIfNotPresent(metadata.getAcquisitionInformation(), info);
         }
-        for (Distribution info : component.getDistributionInfo()) {
+        Distribution di = component.getDistributionInfo();
+        if (di != null) {
             // See Javadoc about why we copy only the distributors.
-            for (Distributor r : info.getDistributors()) {
+            for (Distributor r : di.getDistributors()) {
                 addIfNotPresent(distribution().getDistributors(), r);
             }
         }
@@ -3304,11 +3307,11 @@
         if (featureDescription  == null) featureDescription  = last (DefaultFeatureCatalogueDescription.class, metadata,            DefaultMetadata::getContentInfo);
         if (acquisition         == null) acquisition         = last (DefaultAcquisitionInformation.class,      metadata,            DefaultMetadata::getAcquisitionInformation);
         if (lineage             == null) lineage             = last (DefaultLineage.class,                     metadata,            DefaultMetadata::getResourceLineages);
-        if (distribution        == null) distribution        = last (DefaultDistribution.class,                metadata,            DefaultMetadata::getDistributionInfo);
+        if (distribution        == null) distribution        = fetch(DefaultDistribution.class,                metadata,            DefaultMetadata::getDistributionInfo);
         if (citation            == null) citation            = fetch(DefaultCitation.class,                    identification,      AbstractIdentification::getCitation);
         if (extent              == null) extent              = last (DefaultExtent.class,                      identification,      AbstractIdentification::getExtents);
         if (constraints         == null) constraints         = last (DefaultLegalConstraints.class,            identification,      AbstractIdentification::getResourceConstraints);
-        if (responsibility      == null) responsibility      = last (DefaultResponsibility.class,              citation,            DefaultCitation::getCitedResponsibleParties);
+        if (responsibility      == null) responsibility      = last (DefaultResponsibleParty.class,            citation,            DefaultCitation::getCitedResponsibleParties);
         if (party               == null) party               = last (AbstractParty.class,                      responsibility,      DefaultResponsibility::getParties);
         if (attributeGroup      == null) attributeGroup      = last (DefaultAttributeGroup.class,              coverageDescription, DefaultCoverageDescription::getAttributeGroups);
         if (sampleDimension     == null) sampleDimension     = last (DefaultSampleDimension.class,             attributeGroup,      DefaultAttributeGroup::getAttributes);
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 2313b9c..e9a9304 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
@@ -47,12 +47,12 @@
 import org.apache.sis.util.resources.Errors;
 import static org.apache.sis.pending.jdk.JDK18.ceilDiv;
 
+// 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 geoapi-4.0 branch:
-import org.opengis.coordinate.MismatchedDimensionException;
-
 
 /**
  * Base class of grid coverage read from a resource where data are stored in tiles.
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/TimeEncoding.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/TimeEncoding.java
index 1571c6b..8fe13bc 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/TimeEncoding.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/TimeEncoding.java
@@ -64,7 +64,7 @@
      * Creates a new time encoding.
      */
     TimeEncoding(final TemporalDatum datum, final Unit<Time> unit) {
-        this.origin   = TemporalDate.toInstant(datum.getOrigin(), null);
+        this.origin   = TemporalDate.toInstant(datum.getOrigin());
         this.interval = unit.getConverterTo(Units.SECOND).convert(1);
     }
 
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 d828d41..0b1c196 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
@@ -310,8 +310,9 @@
         for (final DirectPosition p : points) {
             final int dimension = p.getDimension();
             if (dimension != DIMENSION) {
-                throw new MismatchedDimensionException(Errors.format(Errors.Keys.MismatchedDimension_3,
-                            Strings.toIndexed("points", i), DIMENSION, dimension));
+                throw new org.opengis.geometry.MismatchedDimensionException(
+                        Errors.format(Errors.Keys.MismatchedDimension_3,
+                        Strings.toIndexed("points", i), DIMENSION, dimension));
             }
             i++;
             final double x,y;
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 d3fdcef..dedfb85 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
@@ -403,8 +403,9 @@
             for (final DirectPosition p : points) {
                 final int dimension = p.getDimension();
                 if (dimension != DIMENSION) {
-                    throw new MismatchedDimensionException(Errors.format(Errors.Keys.MismatchedDimension_3,
-                                Strings.toIndexed("points", i), DIMENSION, dimension));
+                    throw new org.opengis.geometry.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;
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 3b170fd..4c9a7d9 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
@@ -109,14 +109,14 @@
      *
      * @see org.apache.sis.util.Version
      */
-    public static final int MAJOR_VERSION = 2;
+    public static final int MAJOR_VERSION = 1;
 
     /**
      * The minor version number of all Apache SIS modules.
      *
      * @see org.apache.sis.util.Version
      */
-    public static final int MINOR_VERSION = 0;
+    public static final int MINOR_VERSION = 9;
 
     /**
      * 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/util/ArgumentChecks.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/ArgumentChecks.java
index 22ee97d..391a52b 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
@@ -771,7 +771,7 @@
             if (cs != null) {                                       // Should never be null, but let be safe.
                 final int dimension = cs.getDimension();
                 if (dimension != expected) {
-                    throw new MismatchedDimensionException(Errors.format(
+                    throw new org.opengis.geometry.MismatchedDimensionException(Errors.format(
                             Errors.Keys.MismatchedDimension_3, name, expected, dimension));
                 }
             }
@@ -796,7 +796,7 @@
         if (cs != null) {
             final int dimension = cs.getDimension();
             if (dimension != expected) {
-                throw new MismatchedDimensionException(Errors.format(
+                throw new org.opengis.geometry.MismatchedDimensionException(Errors.format(
                         Errors.Keys.MismatchedDimension_3, name, expected, dimension));
             }
         }
@@ -820,7 +820,7 @@
         if (indices != null) {
             final int dimension = indices.length;
             if (dimension != expected) {
-                throw new MismatchedDimensionException(Errors.format(
+                throw new org.opengis.geometry.MismatchedDimensionException(Errors.format(
                         Errors.Keys.MismatchedDimension_3, name, expected, dimension));
             }
         }
@@ -842,7 +842,7 @@
         if (vector != null) {
             final int dimension = vector.length;
             if (dimension != expected) {
-                throw new MismatchedDimensionException(Errors.format(
+                throw new org.opengis.geometry.MismatchedDimensionException(Errors.format(
                         Errors.Keys.MismatchedDimension_3, name, expected, dimension));
             }
         }
@@ -864,7 +864,7 @@
         if (position != null) {
             final int dimension = position.getDimension();
             if (dimension != expected) {
-                throw new MismatchedDimensionException(Errors.format(
+                throw new org.opengis.geometry.MismatchedDimensionException(Errors.format(
                         Errors.Keys.MismatchedDimension_3, name, expected, dimension));
             }
         }
@@ -886,7 +886,7 @@
         if (envelope != null) {
             final int dimension = envelope.getDimension();
             if (dimension != expected) {
-                throw new MismatchedDimensionException(Errors.format(
+                throw new org.opengis.geometry.MismatchedDimensionException(Errors.format(
                         Errors.Keys.MismatchedDimension_3, name, expected, dimension));
             }
         }
@@ -910,7 +910,7 @@
         if (envelope != null) {
             final int dimension = envelope.getDimension();
             if (dimension != expected) {
-                throw new MismatchedDimensionException(Errors.format(
+                throw new org.opengis.geometry.MismatchedDimensionException(Errors.format(
                         Errors.Keys.MismatchedDimension_3, name, expected, dimension));
             }
         }
@@ -943,8 +943,9 @@
                 expectedSource = expectedTarget;
                 side = 1;
             }
-            throw new MismatchedDimensionException(Errors.format(Errors.Keys.MismatchedTransformDimension_4,
-                                                                 name, side, expectedSource, dimension));
+            throw new org.opengis.geometry.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/Deprecable.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/Deprecable.java
index cf1a4d7..57ffce9 100644
--- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/Deprecable.java
+++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/Deprecable.java
@@ -18,9 +18,6 @@
 
 import org.opengis.util.InternationalString;
 
-// Specific to the geoapi-4.0 branch:
-import java.util.Optional;
-
 
 /**
  * Interface of classes for which deprecated instances may exist. Despite the name, the entities deprecated
@@ -62,10 +59,10 @@
      *
      * <div class="note"><b>Example:</b> "superseded by code XYZ".</div>
      *
-     * @return comments about this instance, or empty if none. Shall be the reason for deprecation
+     * @return comments about this instance, or {@code null} if none. Shall be the reason for deprecation
      *         or the alternative to use if this instance {@linkplain #isDeprecated() is deprecated}.
      */
-    default Optional<InternationalString> getRemarks() {
-        return Optional.empty();
+    default InternationalString getRemarks() {
+        return null;
     }
 }
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 1564f4f..90397af 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,6 +51,8 @@
 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;
@@ -145,7 +147,12 @@
     /**
      * Dummy class for {@link #testGetLeafInterfaces()}.
      */
-    private abstract static class T1 implements GeographicCRS {}
+    @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 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 e6ecb33..6f449ee 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
@@ -46,7 +46,7 @@
 
     /**
      * Creates a new set filled with up to 4 axis directions.
-     * The directions are (NORTH_NORTH_EAST, EAST, UP, FUTURE) in that order.
+     * The directions are (NORTH, EAST, UP, FUTURE) in that order.
      *
      * @param n Number of code list to add.
      */
@@ -59,7 +59,7 @@
             case 4: assertTrue(c.add(FUTURE));              // Fallthrough everywhere.
             case 3: assertTrue(c.add(UP));
             case 2: assertTrue(c.add(EAST));
-            case 1: assertTrue(c.add(NORTH_NORTH_EAST));
+            case 1: assertTrue(c.add(NORTH));
             case 0: break;
         }
         assertEquals(n, c.size());
@@ -68,12 +68,12 @@
 
     /**
      * Creates a code list of another kind. The returned set contains a code list having
-     * the same ordinal value as {@link AxisDirection#NORTH_NORTH_EAST}, so we can detect
-     * if the {@code SortedSet} confuses the code list types.
+     * the same ordinal value as {@link AxisDirection#NORTH}, so we can detect if the
+     * {@code SortedSet} confuses the code list types.
      */
     private CodeListSet<OnLineFunction> createOtherKind() {
         // For the validity of the tests, ordinal value must be the same.
-        assertEquals(NORTH_NORTH_EAST.ordinal(), OnLineFunction.INFORMATION.ordinal());
+        assertEquals(NORTH.ordinal(), OnLineFunction.INFORMATION.ordinal());
         final CodeListSet<OnLineFunction> c = new CodeListSet<>(OnLineFunction.class);
         assertTrue(c.add(OnLineFunction.INFORMATION));
         return c;
@@ -86,7 +86,7 @@
     @Test
     public void testToArray() {
         final CodeListSet<AxisDirection> c = create(4);
-        assertArrayEquals(new Object[] {NORTH_NORTH_EAST, EAST, UP, FUTURE}, c.toArray());
+        assertArrayEquals(new Object[] {NORTH, EAST, UP, FUTURE}, c.toArray());
     }
 
     /**
@@ -106,7 +106,7 @@
     @Test
     public void testContains() {
         final CodeListSet<AxisDirection> c = create(4);
-        assertTrue (c.contains(NORTH_NORTH_EAST));
+        assertTrue (c.contains(NORTH));
         assertFalse(c.contains(SOUTH));
         assertTrue (c.contains(FUTURE));
         assertFalse(c.contains(PAST));
@@ -128,9 +128,9 @@
         assertFalse(c.remove(null), "Should be null-safe.");
         assertFalse(c.remove(OnLineFunction.INFORMATION), "Code list of other kind should not be included.");
 
-        assertTrue (c.remove  (NORTH_NORTH_EAST));
+        assertTrue (c.remove  (NORTH));
         assertFalse(c.remove  (SOUTH));
-        assertFalse(c.contains(NORTH_NORTH_EAST));
+        assertFalse(c.contains(NORTH));
         assertEquals(3, c.size());
 
         assertTrue (c.remove  (FUTURE));
@@ -151,7 +151,7 @@
         final CodeListSet<AxisDirection> c = create(4);
         final CodeListSet<AxisDirection> o = create(4);
         assertTrue (c.containsAll(o));
-        assertTrue (o.remove(NORTH_NORTH_EAST));
+        assertTrue (o.remove(NORTH));
         assertTrue (o.remove(FUTURE));
         assertTrue (c.containsAll(o));
         assertTrue (o.add(NORTH_EAST));
@@ -185,7 +185,7 @@
         assertTrue(o.add(NORTH_EAST));      // Extra value shall be ignored.
 
         assertTrue(c.retainAll(o));
-        assertArrayEquals(new Object[] {NORTH_NORTH_EAST, EAST}, c.toArray());
+        assertArrayEquals(new Object[] {NORTH, EAST}, c.toArray());
         assertFalse(c.retainAll(o));        // Invoking a second time should not make any difference.
         assertEquals(2, c.size());
         assertTrue(c.retainAll(createOtherKind()));
@@ -202,7 +202,7 @@
         assertTrue(c.add(NORTH_EAST));
 
         assertTrue(c.addAll(o));
-        assertArrayEquals(new Object[] {NORTH_NORTH_EAST, NORTH_EAST, EAST, UP}, c.toArray());
+        assertArrayEquals(new Object[] {NORTH, NORTH_EAST, EAST, UP}, c.toArray());
         assertFalse(c.addAll(o));       // Invoking a second time should not make any difference.
     }
 
@@ -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.NORTH, AxisDirection.NORTH_NORTH_EAST, "));
+        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/geoapi/build.gradle.kts b/geoapi/build.gradle.kts
index 5b12e74..3ce4abe 100644
--- a/geoapi/build.gradle.kts
+++ b/geoapi/build.gradle.kts
@@ -26,6 +26,6 @@
     inputs.dir("snapshot/geoapi-pending/src/main")
     inputs.dir("snapshot/geoapi-conformance/src/main")
 
-    outputs.file("snapshot/geoapi-pending/target/geoapi-pending-4.0-SNAPSHOT.jar")
-    outputs.file("snapshot/geoapi-conformance/target/geoapi-conformance-4.0-SNAPSHOT.jar")
+    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
index ccc41ee..ed7e9f0 160000
--- a/geoapi/snapshot
+++ b/geoapi/snapshot
@@ -1 +1 @@
-Subproject commit ccc41ee613b7451366a796fada91c1f74f8f7168
+Subproject commit ed7e9f0463a9b5eb0f9181da5ca070d0b79375ac
diff --git a/gradle.properties b/gradle.properties
index df07faa..e9b032a 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=2.0-SNAPSHOT
+version=1.x-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/netbeans-project/build.xml b/netbeans-project/build.xml
index 0f39937..47443fb 100644
--- a/netbeans-project/build.xml
+++ b/netbeans-project/build.xml
@@ -34,9 +34,9 @@
         <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-4.0-SNAPSHOT.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-4.0-SNAPSHOT.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/nbproject/build-impl.xml b/netbeans-project/nbproject/build-impl.xml
index c3ef739..c68a938 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_4.0-impl">
+<project xmlns:if="ant:if" xmlns:unless="ant:unless" basedir=".." default="default" name="Apache_SIS_on_GeoAPI_3.1-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_4.0" 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.1" 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_4.0 -testname @{testClass} ${test.class.or.method}" property="testng.cmd.args" value="@{testClass}">
+                <condition else="-suitename Apache_SIS_on_GeoAPI_3.1 -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 4.0 was already built"/>
+        <echo level="warn" message="Cycle detected: Apache SIS on GeoAPI 3.1 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 4.0 was already built"/>
+        <echo level="warn" message="Cycle detected: Apache SIS on GeoAPI 3.1 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 626e399..b5d9e58 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=ee8bdb1f
-nbproject/build-impl.xml.script.CRC32=9782532a
-nbproject/build-impl.xml.stylesheet.CRC32=d1ebcf0f@1.24
+nbproject/build-impl.xml.data.CRC32=e365e8d0
+nbproject/build-impl.xml.script.CRC32=3c96dd2f
+nbproject/build-impl.xml.stylesheet.CRC32=d1ebcf0f@1.22
diff --git a/netbeans-project/nbproject/project.xml b/netbeans-project/nbproject/project.xml
index 8fce63d..57af576 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 4.0</name>
+            <name>Apache SIS on GeoAPI 3.1</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/main/org/apache/sis/gui/metadata/IdentificationInfo.java b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/metadata/IdentificationInfo.java
index 05622dd..855e981 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
@@ -60,9 +60,6 @@
 import org.apache.sis.util.resources.Vocabulary;
 import static org.apache.sis.util.privy.CollectionsExt.nonNull;
 
-// Specific to the geoapi-4.0 branch:
-import org.opengis.util.InternationalString;
-
 
 /**
  * The pane where to show the values of {@link Identification} objects.
@@ -301,8 +298,8 @@
             label = Vocabulary.Keys.Purpose;
             text = owner.string(info.getPurpose());
             if (text == null) {
-                for (final InternationalString c : nonNull(info.getCredits())) {
-                    text = owner.string(c);
+                for (final String c : nonNull(info.getCredits())) {
+                    text = c;
                     if (text != null) {
                         label = Vocabulary.Keys.Credit;
                         break;
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 00d4c83..9a11b70 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
@@ -68,12 +68,12 @@
 import org.apache.sis.util.Exceptions;
 import org.apache.sis.util.resources.Vocabulary;
 
+// 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;
 
-// Specific to the geoapi-4.0 branch:
-import org.opengis.referencing.crs.DerivedCRS;
-
 
 /**
  * A list of Coordinate Reference Systems (CRS) from which the user can select.
@@ -370,6 +370,7 @@
     /**
      * 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);
@@ -389,14 +390,14 @@
         String text = Vocabulary.forLocale(locale).getString(key);
         final int     dimension = ReferencingUtilities.getDimension(crs);
         final boolean addDimension = (dimension != expected && expected != 0);
-        final boolean isProjection = (crs instanceof DerivedCRS);
+        final boolean isProjection = (crs instanceof GeneralDerivedCRS);
         if (addDimension | isProjection) {
             final StringBuilder buffer = new StringBuilder(text);
             if (addDimension) {
                 buffer.append(" (").append(dimension).append("D)");
             }
             if (isProjection) {
-                final Conversion conversion = ((DerivedCRS) crs).getConversionFromBase();
+                final Conversion conversion = ((GeneralDerivedCRS) crs).getConversionFromBase();
                 if (conversion != null) {
                     final OperationMethod method = conversion.getMethod();
                     if (method != null) {
diff --git a/parent/pom.xml b/parent/pom.xml
index 755907c..000fb61 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -38,7 +38,7 @@
        ============================================================== -->
   <groupId>org.apache.sis</groupId>
   <artifactId>parent</artifactId>
-  <version>2.0-SNAPSHOT</version>
+  <version>1.x-SNAPSHOT</version>
   <packaging>pom</packaging>
 
   <name>Apache SIS</name>
diff --git a/settings.gradle.kts b/settings.gradle.kts
index d86d91b..d951c0e 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -14,8 +14,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-rootProject.name  = "Apache SIS on GeoAPI 4.0"
-val geoapiVersion = "4.0-SNAPSHOT"
+rootProject.name  = "Apache SIS on GeoAPI 3.1"
+val geoapiVersion = "3.1-SNAPSHOT"
 
 /*
  * The sub-projects to include in the build.