Merge branch 'geoapi-4.0' into geoapi-3.1, except for `IdentifiedObject.getRemarks()` which is kept unchanged for compatibility with GeoAPI 3.0.
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 8dfe7c0..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
@@ -19,6 +19,7 @@
 import java.util.Map;
 import java.util.Locale;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.logging.Logger;
 import java.io.Serializable;
 import org.opengis.util.NameFactory;
@@ -38,7 +39,7 @@
  * Identification and description information inherited by property types and feature types.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.4
+ * @version 1.5
  * @since   0.5
  */
 public class AbstractIdentifiedType implements IdentifiedType, Deprecable, Serializable {
@@ -280,11 +281,11 @@
      * Returns a natural language designator for the element.
      * This can be used as an alternative to the {@linkplain #getName() name} in user interfaces.
      *
-     * @return natural language designator for the element, or {@code null} if none.
+     * @return natural language designator for the element.
      */
     @Override
-    public InternationalString getDesignation() {
-        return designation;
+    public Optional<InternationalString> getDesignation() {
+        return Optional.ofNullable(designation);
     }
 
     /**
@@ -297,8 +298,8 @@
      * @return information beyond that required for concise definition of the element, or {@code null} if none.
      */
     @Override
-    public InternationalString getDescription() {
-        return description;
+    public Optional<InternationalString> getDescription() {
+        return Optional.ofNullable(description);
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/AbstractOperation.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/AbstractOperation.java
index 7a2ef59..2ce24a1 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/AbstractOperation.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/AbstractOperation.java
@@ -145,8 +145,8 @@
         if (properties.isEmpty()) {
             properties.put(NAME_KEY,        super.getName());           // Do not invoke user-overrideable method.
             properties.put(DEFINITION_KEY,  super.getDefinition());
-            properties.put(DESIGNATION_KEY, super.getDesignation());
-            properties.put(DESCRIPTION_KEY, super.getDescription());
+            super.getDesignation().ifPresent((i18n) -> properties.put(DESIGNATION_KEY, i18n));
+            super.getDescription().ifPresent((i18n) -> properties.put(DESCRIPTION_KEY, i18n));
         }
         return properties;
     }
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 32c9b4d..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
@@ -335,7 +335,7 @@
             boolean hasDeprecatedTypes = false;
             for (final PropertyType propertyType : featureType.getProperties(true)) {
                 if (!hasDesignation) {
-                    hasDesignation = propertyType.getDesignation() != null;
+                    hasDesignation = propertyType.getDesignation().isPresent();
                 }
                 if (!hasCharacteristics && propertyType instanceof AttributeType<?>) {
                     hasCharacteristics = !((AttributeType<?>) propertyType).characteristics().isEmpty();
@@ -486,8 +486,9 @@
                      * In many cases, this information is not provided and the whole column is skipped.
                      */
                     case DESIGNATION: {
-                        final InternationalString d = propertyType.getDesignation();
-                        if (d != null) table.append(d.toString(displayLocale));
+                        propertyType.getDesignation().ifPresent((d) -> {
+                            table.append(d.toString(displayLocale));
+                        });
                         break;
                     }
                     /*
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/builder/TypeBuilder.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/builder/TypeBuilder.java
index a139fae..3891439 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/builder/TypeBuilder.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/builder/TypeBuilder.java
@@ -121,8 +121,8 @@
     final void initialize(final IdentifiedType template) {
         putIfNonNull(AbstractIdentifiedType.NAME_KEY,        template.getName());
         putIfNonNull(AbstractIdentifiedType.DEFINITION_KEY,  template.getDefinition());
-        putIfNonNull(AbstractIdentifiedType.DESIGNATION_KEY, template.getDesignation());
-        putIfNonNull(AbstractIdentifiedType.DESCRIPTION_KEY, template.getDescription());
+        putIfNonNull(AbstractIdentifiedType.DESIGNATION_KEY, template.getDesignation().orElse(null));
+        putIfNonNull(AbstractIdentifiedType.DESCRIPTION_KEY, template.getDescription().orElse(null));
         if (template instanceof Deprecable && ((Deprecable) template).isDeprecated()) {
             identification.put(AbstractIdentifiedType.DEPRECATED_KEY, Boolean.TRUE);
         }
diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/DefaultAttributeTypeTest.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/DefaultAttributeTypeTest.java
index de9f152..00fec97 100644
--- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/DefaultAttributeTypeTest.java
+++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/DefaultAttributeTypeTest.java
@@ -127,8 +127,7 @@
         assertInstanceOf(LocalName.class, name);
         assertEquals("city", name.toString());
 
-        InternationalString p = city.getDesignation();
-        assertNotNull(p);
+        InternationalString p = city.getDesignation().orElseThrow();
         assertEquals("City",  p.toString(Locale.ENGLISH));
         assertEquals("Ville", p.toString(Locale.FRENCH));
         assertEquals("都市",   p.toString(Locale.JAPANESE));
@@ -138,7 +137,7 @@
         assertEquals("Le nom de la ville.", p.toString(Locale.FRENCH));
         assertEquals("都市の名前。", p.toString(Locale.JAPANESE));
 
-        p = city.getDescription();
+        p = city.getDescription().orElseThrow();
         assertEquals("Some verbose description.", p.toString(Locale.ENGLISH));
         assertEquals(String.class, city.getValueClass());
         assertEquals("Utopia",     city.getDefaultValue());
diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/builder/AssociationRoleBuilderTest.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/builder/AssociationRoleBuilderTest.java
index 17142dd..d90e1d7 100644
--- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/builder/AssociationRoleBuilderTest.java
+++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/builder/AssociationRoleBuilderTest.java
@@ -54,8 +54,8 @@
         final var role = builder.build();
         assertEquals(1, role.getMinimumOccurs());
         assertEquals(2, role.getMaximumOccurs());
-        assertEquals(new SimpleInternationalString("A designation"),          role.getDesignation());
+        assertEquals(new SimpleInternationalString("A designation"),          role.getDesignation().orElseThrow());
         assertEquals(new SimpleInternationalString("A definition"),           role.getDefinition());
-        assertEquals(new SimpleInternationalString("Bridges on the highway"), role.getDescription());
+        assertEquals(new SimpleInternationalString("Bridges on the highway"), role.getDescription().orElseThrow());
     }
 }
diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/builder/AttributeTypeBuilderTest.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/builder/AttributeTypeBuilderTest.java
index b61fa09..4ddd00c 100644
--- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/builder/AttributeTypeBuilderTest.java
+++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/builder/AttributeTypeBuilderTest.java
@@ -66,8 +66,8 @@
         assertEquals(String.class, attribute.getValueClass());
         assertNull(attribute.getDefaultValue());
         assertNull(attribute.getDefinition());
-        assertNull(attribute.getDescription());
-        assertNull(attribute.getDesignation());
+        assertTrue(attribute.getDescription().isEmpty());
+        assertTrue(attribute.getDesignation().isEmpty());
         assertEquals(1, attribute.getMinimumOccurs());
         assertEquals(1, attribute.getMaximumOccurs());
     }
@@ -89,8 +89,8 @@
 
         assertEquals("myScope:myName",      attribute.getName().toString());
         assertEquals("test definition",     attribute.getDefinition().toString());
-        assertEquals("test description",    attribute.getDescription().toString());
-        assertEquals("test designation",    attribute.getDesignation().toString());
+        assertEquals("test description",    attribute.getDescription().orElseThrow().toString());
+        assertEquals("test designation",    attribute.getDesignation().orElseThrow().toString());
         assertEquals(String.class,          attribute.getValueClass());
         assertEquals("test default value.", attribute.getDefaultValue());
         assertEquals(10,                    attribute.getMinimumOccurs());
@@ -142,8 +142,8 @@
         final var attribute = b2.build();
         assertEquals("temperature",      attribute.getName().toString());
         assertEquals("test definition",  attribute.getDefinition().toString());
-        assertEquals("test description", attribute.getDescription().toString());
-        assertEquals("test designation", attribute.getDesignation().toString());
+        assertEquals("test description", attribute.getDescription().orElseThrow().toString());
+        assertEquals("test designation", attribute.getDesignation().orElseThrow().toString());
         assertEquals(Double.class,       attribute.getValueClass());
         assertEquals(Double.valueOf(25), attribute.getDefaultValue());
     }
diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/builder/CharacteristicTypeBuilderTest.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/builder/CharacteristicTypeBuilderTest.java
index d9dd845..0da391b 100644
--- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/builder/CharacteristicTypeBuilderTest.java
+++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/builder/CharacteristicTypeBuilderTest.java
@@ -81,8 +81,8 @@
         final var attribute = b2.build();
         assertEquals("stddev",           attribute.getName().toString());
         assertEquals("test definition",  attribute.getDefinition().toString());
-        assertEquals("test description", attribute.getDescription().toString());
-        assertEquals("test designation", attribute.getDesignation().toString());
+        assertEquals("test description", attribute.getDescription().orElseThrow().toString());
+        assertEquals("test designation", attribute.getDesignation().orElseThrow().toString());
         assertEquals(Float.class,        attribute.getValueClass());
         assertEquals(Float.valueOf(2),   attribute.getDefaultValue());
     }
diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/builder/FeatureTypeBuilderTest.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/builder/FeatureTypeBuilderTest.java
index 9053be2..3e4f9bb 100644
--- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/builder/FeatureTypeBuilderTest.java
+++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/builder/FeatureTypeBuilderTest.java
@@ -137,8 +137,8 @@
         final var feature = builder.build();
         assertEquals("myScope:myName",   feature.getName().toString());
         assertEquals("test definition",  feature.getDefinition().toString());
-        assertEquals("test description", feature.getDescription().toString());
-        assertEquals("test designation", feature.getDesignation().toString());
+        assertEquals("test description", feature.getDescription().orElseThrow().toString());
+        assertEquals("test designation", feature.getDesignation().orElseThrow().toString());
         assertTrue  (                    feature.isAbstract());
 
         final var it = feature.getProperties(true).iterator();
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/PropertyAccessor.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/PropertyAccessor.java
index c80e2d7..45382ab 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/PropertyAccessor.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/PropertyAccessor.java
@@ -348,7 +348,7 @@
              * is to get a type which can be accepted by the setter.
              */
             Class<?> elementType = getter.getReturnType();
-            if (Collection.class.isAssignableFrom(elementType)) {
+            if (Collection.class.isAssignableFrom(elementType) || Classes.isParameterizedProperty(elementType)) {
                 elementType = Classes.boundOfParameterizedProperty(getter);
                 if (elementType == null) {
                     // Subclass has erased parameterized type. Use method declared in the interface.
@@ -595,7 +595,8 @@
                     return elementTypes[index];
                 }
                 case PROPERTY_TYPE: {
-                    return getters[index].getReturnType();
+                    final Class<?> type = getters[index].getReturnType();
+                    return Classes.isParameterizedProperty(type) ? elementTypes[index] : type;
                 }
                 case DECLARING_INTERFACE: {
                     return getters[index].getDeclaringClass();
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/TypeValuePolicy.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/TypeValuePolicy.java
index 193e2c8..179b703 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/TypeValuePolicy.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/TypeValuePolicy.java
@@ -31,19 +31,20 @@
  */
 public enum TypeValuePolicy {
     /**
-     * The type of a property, as inferred from the
-     * {@linkplain java.lang.reflect.Method#getReturnType() return type} of the property method
-     * defined in the interface.
+     * The type of a property as inferred from the return type of the property method defined in the interface.
+     * The property type is the {@linkplain java.lang.reflect.Method#getReturnType()} except for the following
+     * special cases:
      *
-     * <h4>Notes</h4>
      * <ul>
-     *   <li>Collections are not handled in any special way: if the return type is a collection,
-     *       then the property type is {@code Collection.class} or any other declared return type.</li>
-     *   <li>As a special case, values of type {@code double} (the primitive type) in
+     *   <li>{@link java.util.Optional} are replaced by the wrapped type.</li>
+     *   <li>Values of type {@code double} (the primitive type) in
      *       {@link org.opengis.metadata.extent.GeographicBoundingBox} are wrapped in
      *       {@link org.apache.sis.measure.Longitude} and {@link org.apache.sis.measure.Latitude}
      *       objects instead of {@link Double}.</li>
      * </ul>
+     *
+     * Note that collections are not handled in any special way. If the return type is a collection,
+     * then the property type is {@code Collection.class} or any other declared return type.
      */
     PROPERTY_TYPE,
 
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 441c62d..5ea5599 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
@@ -17,6 +17,7 @@
 package org.apache.sis.metadata.simple;
 
 import java.util.Objects;
+import java.util.Optional;
 import java.io.Serializable;
 import org.opengis.util.InternationalString;
 import org.opengis.metadata.Identifier;
@@ -124,12 +125,12 @@
      * Returns a narrative explanation of the role of this object.
      * The default implementation returns {@link Identifier#getDescription()}.
      *
-     * @return a narrative explanation of the role of this object, or {@code null} if none.
+     * @return a narrative explanation of the role of this object.
      */
-    public InternationalString getDescription() {
+    public Optional<InternationalString> getDescription() {
         @SuppressWarnings("LocalVariableHidesMemberVariable")
         final Identifier name = this.name;
-        return (name != null) ? name.getDescription() : null;
+        return Optional.ofNullable((name != null) ? name.getDescription() : null);
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/Dispatcher.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/Dispatcher.java
index ed15523..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
@@ -297,7 +297,7 @@
      */
     final String error(final Method method) {
         Class<?> returnType = method.getReturnType();
-        if (Collection.class.isAssignableFrom(returnType)) {
+        if (Classes.isParameterizedProperty(returnType) || Collection.class.isAssignableFrom(returnType)) {
             final Class<?> elementType = Classes.boundOfParameterizedProperty(method);
             if (elementType != null) {
                 returnType = elementType;
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/MetadataSource.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/MetadataSource.java
index 49ad8d1..b1e7fff 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/MetadataSource.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/MetadataSource.java
@@ -985,7 +985,7 @@
         final Class<?> type           = TableHierarchy.subType(info.getMetadataType(), toSearch.identifier);
         final Class<?> returnType     = method.getReturnType();
         final boolean  wantCollection = Collection.class.isAssignableFrom(returnType);
-        final Class<?> elementType    = wantCollection ? Classes.boundOfParameterizedProperty(method) : returnType;
+        final Class<?> elementType    = (wantCollection || Classes.isParameterizedProperty(returnType)) ? Classes.boundOfParameterizedProperty(method) : returnType;
         final boolean  isMetadata     = standard.isMetadata(elementType);
         final String   tableName      = getTableName(type);
         final String   columnName     = info.asNameMap(standard).get(method.getName());
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 b05975e..7ca4142 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
@@ -17,6 +17,7 @@
 package org.apache.sis.xml.bind.metadata.replace;
 
 import java.util.Map;
+import java.util.Optional;
 import jakarta.xml.bind.annotation.XmlType;
 import jakarta.xml.bind.annotation.XmlElement;
 import jakarta.xml.bind.annotation.XmlRootElement;
@@ -140,10 +141,9 @@
             code = id.getCode();
             definition = id.getDescription();
         }
-        InternationalString text = parameter.getDescription();
-        if (text != null) {
+        parameter.getDescription().ifPresent((text) -> {
             description = new DefaultMeasureDescription(text);
-        }
+        });
         valueType = parameter.getValueType();
         valueStructure = ValueStructure.valueOf(parameter.getValueClass()).orElse(null);
     }
@@ -181,12 +181,13 @@
     /**
      * Returns a narrative explanation of the role of the parameter.
      *
-     * @return a narrative explanation of the role of the parameter, or {@code null} if none.
+     * @return a narrative explanation of the role of the parameter.
      */
     @Override
-    public InternationalString getDescription() {
+    public Optional<InternationalString> getDescription() {
+        @SuppressWarnings("LocalVariableHidesMemberVariable")
         final Description description = this.description;
-        return (description != null) ? description.getTextDescription() : null;
+        return Optional.ofNullable((description != null) ? description.getTextDescription() : null);
     }
 
     /**
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 a6b2c50..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
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.xml.bind.metadata.replace;
 
+import java.util.Optional;
 import jakarta.xml.bind.annotation.XmlType;
 import jakarta.xml.bind.annotation.XmlElement;
 import jakarta.xml.bind.annotation.XmlRootElement;
@@ -163,7 +164,7 @@
         super(parameter);
         memberName    = getMemberName(parameter);
         direction     = parameter.getDirection();
-        description   = parameter.getDescription();
+        description   = parameter.getDescription().orElse(null);
         optionality   = parameter.getMinimumOccurs() > 0;
         repeatability = parameter.getMaximumOccurs() > 1;
     }
@@ -338,11 +339,11 @@
     /**
      * Returns a narrative explanation of the role of the parameter.
      *
-     * @return a narrative explanation of the role of the parameter, or {@code null} if none.
+     * @return a narrative explanation of the role of the parameter.
      */
     @Override
-    public InternationalString getDescription() {
-        return description;
+    public Optional<InternationalString> getDescription() {
+        return Optional.ofNullable(description);
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/bind/metadata/replace/QualityParameterTest.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/bind/metadata/replace/QualityParameterTest.java
index 28fe198..6ea99d0 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/bind/metadata/replace/QualityParameterTest.java
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/bind/metadata/replace/QualityParameterTest.java
@@ -70,8 +70,8 @@
         final Identifier name = param.getName();
         assertNull  (name.getCodeSpace());
         assertEquals("some parameter", name.getCode());
-        assertEquals("a definition",  String.valueOf(name .getDescription()));
-        assertEquals("a description", String.valueOf(param.getDescription()));
+        assertEquals("a definition",   name.getDescription().toString());
+        assertEquals("a description",  param.getDescription().orElseThrow().toString());
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/bind/metadata/replace/ServiceParameterTest.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/bind/metadata/replace/ServiceParameterTest.java
index aa31385..4d67ae3 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/bind/metadata/replace/ServiceParameterTest.java
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/bind/metadata/replace/ServiceParameterTest.java
@@ -69,8 +69,8 @@
         final Identifier name = param.getName();
         assertEquals("TestSpace", name.getCodeSpace());
         assertEquals("My service parameter", name.getCode());
-        assertEquals("TestSpace:My service parameter", String.valueOf(name));
-        assertNull  (param.getDescription());
+        assertEquals("TestSpace:My service parameter", name.toString());
+        assertTrue(param.getDescription().isEmpty());
     }
 
     /**
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 ca1dcbd..ec6d204 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
@@ -353,7 +353,7 @@
         for (final Method method : type.getDeclaredMethods()) {
             Class<?> valueType = method.getReturnType();
             final boolean isCollection = Collection.class.isAssignableFrom(valueType);
-            if (isCollection) {
+            if (isCollection || Classes.isParameterizedProperty(valueType)) {
                 valueType = Classes.boundOfParameterizedProperty(method);
             }
             verify(method, method.getName(), valueType, isCollection);
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 a34a79c..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
@@ -24,6 +24,7 @@
 import java.util.Iterator;
 import java.util.Locale;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.Formattable;
 import java.util.FormattableFlags;
 import java.util.function.Function;
@@ -132,7 +133,7 @@
  * @since   0.4
  */
 @XmlType(name = "IdentifiedObjectType", propOrder = {
-    "description",
+    "descriptionGML",
     "identifier",
     "names",
     "remarks",
@@ -239,6 +240,7 @@
      * @see #getRemarks()
      */
     @SuppressWarnings("serial")         // Most SIS implementations are serializable.
+    @XmlElement(name = "remarks")
     private InternationalString remarks;
 
     /**
@@ -578,15 +580,14 @@
      * The default implementation returns the {@linkplain ImmutableIdentifier#getDescription() description}
      * provided by this object's {@linkplain #getName() name}.
      *
-     * @return a narrative explanation of the role of this object, or {@code null} if none.
+     * @return a narrative explanation of the role of this object.
      *
      * @see ImmutableIdentifier#getDescription()
      *
      * @since 0.6
      */
-    @XmlElement(name = "description")
-    public InternationalString getDescription() {
-        return (name != null) ? name.getDescription() : null;
+    public Optional<InternationalString> getDescription() {
+        return Optional.ofNullable((name != null) ? name.getDescription() : null);
     }
 
     /**
@@ -597,7 +598,6 @@
      * @return the remarks, or {@code null} if none.
      */
     @Override
-    @XmlElement(name = "remarks")
     public InternationalString getRemarks() {
         return remarks;
     }
@@ -1183,6 +1183,14 @@
     }
 
     /**
+     * Returns a narrative explanation of the role of this object.
+     */
+    @XmlElement(name = "description")
+    private InternationalString getDescriptionGML() {
+        return getDescription().orElse(null);
+    }
+
+    /**
      * Finds the first non-null domain element.
      *
      * @param  <T>     type of domain element to get.
@@ -1254,19 +1262,6 @@
     }
 
     /**
-     * Invoked by JAXB for setting the remarks.
-     *
-     * @see #getRemarks()
-     */
-    private void setRemarks(final InternationalString value) {
-        if (remarks == null) {
-            remarks = value;
-        } else {
-            propertyAlreadySet("setRemarks", "remarks");
-        }
-    }
-
-    /**
      * Logs a warning saying that an unmarshalled property was already set.
      *
      * @param  method  the caller method, used for logging.
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/Properties.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/Properties.java
index 5a3d432..ddbde47 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
@@ -157,7 +157,7 @@
                 }
                 case 7: {   // OPERATION_VERSION_KEY
                     if (object instanceof CoordinateOperation) {
-                        return ((CoordinateOperation) object).getOperationVersion();
+                        return ((CoordinateOperation) object).getOperationVersion().orElse(null);
                     }
                     break;
                 }
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 ea0256a..2f5ffd4 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
@@ -181,6 +181,7 @@
      *
      * @see #getOperationVersion()
      */
+    @XmlElement(name = "operationVersion")
     private String operationVersion;
 
     /**
@@ -422,7 +423,7 @@
         sourceCRS                   = operation.getSourceCRS();
         targetCRS                   = operation.getTargetCRS();
         interpolationCRS            = operation.getInterpolationCRS().orElse(null);
-        operationVersion            = operation.getOperationVersion();
+        operationVersion            = operation.getOperationVersion().orElse(null);
         coordinateOperationAccuracy = operation.getCoordinateOperationAccuracy();
         transform                   = operation.getMathTransform();
         if (operation instanceof AbstractCoordinateOperation) {
@@ -556,12 +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, or {@code null} in none.
+     * @return the coordinate operation version.
      */
     @Override
-    @XmlElement(name = "operationVersion")
-    public String getOperationVersion() {
-        return operationVersion;
+    public Optional<String> getOperationVersion() {
+        return Optional.ofNullable(operationVersion);
     }
 
     /**
@@ -1167,19 +1167,6 @@
     }
 
     /**
-     * Invoked by JAXB only at unmarshalling time.
-     *
-     * @see #getOperationVersion()
-     */
-    private void setOperationVersion(final String value) {
-        if (operationVersion == null) {
-            operationVersion = value;
-        } else {
-            ImplementationHelper.propertyAlreadySet(AbstractCoordinateOperation.class, "setOperationVersion", "operationVersion");
-        }
-    }
-
-    /**
      * Invoked by JAXB after unmarshalling.
      * May be overridden by subclasses.
      */
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 e60f56e..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
@@ -65,10 +65,10 @@
     static final DefaultOperationMethod AFFINE_GEOCENTRIC;
 
     static {
-        final HashMap<String,Object> properties = new HashMap<>(4);
+        final var properties = new HashMap<String,Object>(4);
         properties.put(DefaultOperationMethod.NAME_KEY,    "Undefined parameters");
         properties.put(DefaultOperationMethod.REMARKS_KEY, "Placeholder for what should be a chain of coordinate operations.");
-        final DefaultParameterDescriptorGroup parameters = new DefaultParameterDescriptorGroup(properties, 0, 1);
+        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());
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/TransformedCoordinateSet.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/TransformedCoordinateSet.java
index 373f089..c841441 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/TransformedCoordinateSet.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/TransformedCoordinateSet.java
@@ -43,6 +43,8 @@
  * The result of transforming coordinate tuples using the math transform of a given coordinate operation.
  *
  * @author  Martin Desruisseaux (Geomatys)
+ *
+ * @todo The current implementation is inefficient.
  */
 final class TransformedCoordinateSet extends AbstractCoordinateSet implements UnaryOperator<DirectPosition> {
     /**
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/parameter/ParameterBuilderTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/parameter/ParameterBuilderTest.java
index 218cccc..e523800 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/parameter/ParameterBuilderTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/parameter/ParameterBuilderTest.java
@@ -24,6 +24,7 @@
 // Test dependencies
 import org.junit.jupiter.api.Test;
 import static org.junit.jupiter.api.Assertions.*;
+import static org.apache.sis.referencing.Assertions.assertRemarksEquals;
 import org.apache.sis.test.TestCase;
 
 
@@ -86,14 +87,14 @@
             builder.addName("False northing")                 .create(0, Units.METRE)
         };
         // Tests random properties.
-        assertEquals("EPSG",             parameters[1].getName().getCodeSpace());
-        assertEquals("False easting",    parameters[3].getName().getCode());
-        assertEquals("Some remarks.",    parameters[0].getRemarks().toString());
-        assertEquals(Double.valueOf(84), parameters[1].getMaximumValue());
-        assertEquals(Units.METRE,        parameters[4].getUnit());
-        assertTrue  (                    parameters[1].getAlias().isEmpty());
+        assertEquals("EPSG",                 parameters[1].getName().getCodeSpace());
+        assertEquals("False easting",        parameters[3].getName().getCode());
+        assertRemarksEquals("Some remarks.", parameters[0], null);
+        assertEquals(Double.valueOf(84),     parameters[1].getMaximumValue());
+        assertEquals(Units.METRE,            parameters[4].getUnit());
+        assertTrue  (                        parameters[1].getAlias().isEmpty());
 
-        final GenericName alias = parameters[0].getAlias().iterator().next();
+        GenericName alias = parameters[0].getAlias().iterator().next();
         assertEquals("central_meridian",     alias.tip().toString());
         assertEquals("OGC",                  alias.head().toString());
         assertEquals("OGC:central_meridian", alias.toString());
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 40629d4..18d9269 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
@@ -19,6 +19,7 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.Collection;
+import java.util.Optional;
 import javax.measure.Unit;
 import org.opengis.parameter.ParameterDescriptor;
 import org.opengis.parameter.ParameterValue;
@@ -109,23 +110,23 @@
     {
         assertEquals(valueDomain, Parameters.getValueDomain(descriptor));
         assertEquals(valueDomain, Parameters.getValueDomain(new ParameterDescriptor<T>() {
-            @Override public ReferenceIdentifier      getName()          {return descriptor.getName();}
-            @Override public Collection<GenericName>  getAlias()         {return descriptor.getAlias();}
-            @Override public Set<ReferenceIdentifier> getIdentifiers()   {return descriptor.getIdentifiers();}
-            @Override public InternationalString      getRemarks()       {return descriptor.getRemarks();}
-            @Override public InternationalString      getDescription()   {return descriptor.getDescription();}
-            @Override public ParameterDirection       getDirection()     {return descriptor.getDirection();}
-            @Override public int                      getMinimumOccurs() {return descriptor.getMinimumOccurs();}
-            @Override public int                      getMaximumOccurs() {return descriptor.getMaximumOccurs();}
-            @Override public TypeName                 getValueType()     {return descriptor.getValueType();}
-            @Override public Class<T>                 getValueClass()    {return descriptor.getValueClass();}
-            @Override public Set<T>                   getValidValues()   {return descriptor.getValidValues();}
-            @Override public Comparable<T>            getMinimumValue()  {return descriptor.getMinimumValue();}
-            @Override public Comparable<T>            getMaximumValue()  {return descriptor.getMaximumValue();}
-            @Override public T                        getDefaultValue()  {return descriptor.getDefaultValue();}
-            @Override public Unit<?>                  getUnit()          {return descriptor.getUnit();}
-            @Override public ParameterValue<T>        createValue()      {return descriptor.createValue();}
-            @Override public String                   toWKT()            {return descriptor.toWKT();}
+            @Override public ReferenceIdentifier           getName()          {return descriptor.getName();}
+            @Override public Collection<GenericName>       getAlias()         {return descriptor.getAlias();}
+            @Override public Set<ReferenceIdentifier>      getIdentifiers()   {return descriptor.getIdentifiers();}
+            @Override public InternationalString           getRemarks()       {return descriptor.getRemarks();}
+            @Override public Optional<InternationalString> getDescription()   {return descriptor.getDescription();}
+            @Override public ParameterDirection            getDirection()     {return descriptor.getDirection();}
+            @Override public int                           getMinimumOccurs() {return descriptor.getMinimumOccurs();}
+            @Override public int                           getMaximumOccurs() {return descriptor.getMaximumOccurs();}
+            @Override public TypeName                      getValueType()     {return descriptor.getValueType();}
+            @Override public Class<T>                      getValueClass()    {return descriptor.getValueClass();}
+            @Override public Set<T>                        getValidValues()   {return descriptor.getValidValues();}
+            @Override public Comparable<T>                 getMinimumValue()  {return descriptor.getMinimumValue();}
+            @Override public Comparable<T>                 getMaximumValue()  {return descriptor.getMaximumValue();}
+            @Override public T                             getDefaultValue()  {return descriptor.getDefaultValue();}
+            @Override public Unit<?>                       getUnit()          {return descriptor.getUnit();}
+            @Override public ParameterValue<T>             createValue()      {return descriptor.createValue();}
+            @Override public String                        toWKT()            {return descriptor.toWKT();}
         }));
     }
 
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 45bfd4f..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
@@ -35,6 +35,7 @@
 import static org.apache.sis.test.Assertions.assertMessageContains;
 import static org.apache.sis.test.Assertions.assertSerializedEquals;
 import static org.apache.sis.test.TestUtilities.getSingleton;
+import static org.apache.sis.referencing.Assertions.assertRemarksEquals;
 
 // Specific to the main and geoapi-3.1 branches:
 import org.opengis.referencing.ReferenceIdentifier;
@@ -83,15 +84,15 @@
     {
         Validators.validate(object);
         final var name = object.getName();
-        assertEquals("GRS 1980",                      name.getCode(), "name");
-        assertEquals("EPSG",                          name.getCodeSpace(), "codespace");
-        assertEquals("8.3",                           name.getVersion(), "version");
-        assertEquals("International 1979",            getSingleton(object.getAlias()).toString(), "aliases");
-        assertEquals(name,                            getSingleton(object.getNames()), "names");
-        assertEquals(identifiers,                     object.getIdentifiers(), "identifiers");
-        assertEquals(gmlID,                           object.getID(), "ID");
-        assertEquals("Adopted by IUGG 1979 Canberra", object.getRemarks().toString(Locale.ENGLISH), "remarks");
-        assertEquals("Adopté par IUGG 1979 Canberra", object.getRemarks().toString(Locale.FRENCH), "remarks_fr");
+        assertEquals("GRS 1980",           name.getCode(), "name");
+        assertEquals("EPSG",               name.getCodeSpace(), "codespace");
+        assertEquals("8.3",                name.getVersion(), "version");
+        assertEquals("International 1979", getSingleton(object.getAlias()).toString(), "aliases");
+        assertEquals(name,                 getSingleton(object.getNames()), "names");
+        assertEquals(identifiers,          object.getIdentifiers(), "identifiers");
+        assertEquals(gmlID,                object.getID(), "ID");
+        assertRemarksEquals("Adopted by IUGG 1979 Canberra", object, Locale.ENGLISH);
+        assertRemarksEquals("Adopté par IUGG 1979 Canberra", object, Locale.FRENCH);
         final Code code = object.getIdentifier();
         return (code != null) ? code.getIdentifier() : null;
     }
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/AbstractReferenceSystemTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/AbstractReferenceSystemTest.java
index 6f13ffc..e8cea7e 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/AbstractReferenceSystemTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/AbstractReferenceSystemTest.java
@@ -36,6 +36,7 @@
 import static org.apache.sis.test.TestUtilities.getSingleton;
 import static org.apache.sis.test.Assertions.assertSerializedEquals;
 import static org.apache.sis.referencing.Assertions.assertWktEquals;
+import static org.apache.sis.referencing.Assertions.assertRemarksEquals;
 
 // Specific to the geoapi-3.1 and geoapi-4.0 branches:
 import static org.opengis.referencing.IdentifiedObject.*;
@@ -74,8 +75,8 @@
         assertEquals("This is a name",         reference.getName().getCode());
         assertEquals("This is a scope",        scope.toString(Locale.ROOT));
         assertEquals("Valide dans ce domaine", scope.toString(Locale.FRENCH));
-        assertEquals("There is remarks",       reference.getRemarks().toString(Locale.ENGLISH));
-        assertEquals("Voici des remarques",    reference.getRemarks().toString(Locale.FRENCH));
+        assertRemarksEquals("There is remarks",    reference, Locale.ENGLISH);
+        assertRemarksEquals("Voici des remarques", reference, Locale.FRENCH);
     }
 
     /**
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 0a38012..5d3e3ae 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
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.referencing;
 
+import java.util.Locale;
 import java.util.Collection;
 import java.awt.geom.Rectangle2D;
 import java.awt.geom.RectangularShape;
@@ -24,6 +25,7 @@
 import javax.measure.Unit;
 import org.opengis.geometry.Envelope;
 import org.opengis.geometry.DirectPosition;
+import org.opengis.util.InternationalString;
 import org.opengis.metadata.Identifier;
 import org.opengis.parameter.GeneralParameterValue;
 import org.opengis.parameter.ParameterDescriptor;
@@ -141,6 +143,19 @@
     }
 
     /**
+     * Asserts that the remarks of the given object are equal to the expected value.
+     *
+     * @param expected  the expected remarks, or {@code null}.
+     * @param object    the object for which to test the remarks.
+     * @param locale    the locale to test, or {@code null}.
+     */
+    public static void assertRemarksEquals(final String expected, final IdentifiedObject object, final Locale locale) {
+        InternationalString i18n = object.getRemarks();
+        String remarks = (i18n == null) ? null : (locale != null) ? i18n.toString(locale) : i18n.toString();
+        assertEquals(expected, remarks, "remarks");
+    }
+
+    /**
      * Compares the given coordinate system axis against the expected values.
      *
      * @param name           the expected axis name code.
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/cs/DefaultCartesianCSTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/cs/DefaultCartesianCSTest.java
index 174ccda..4df2612 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/cs/DefaultCartesianCSTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/cs/DefaultCartesianCSTest.java
@@ -34,6 +34,7 @@
 import static org.apache.sis.test.Assertions.assertMessageContains;
 import static org.apache.sis.test.Assertions.assertEqualsIgnoreMetadata;
 import static org.apache.sis.referencing.Assertions.assertAxisEquals;
+import static org.apache.sis.referencing.Assertions.assertRemarksEquals;
 import static org.apache.sis.referencing.Assertions.assertEpsgIdentifierEquals;
 
 
@@ -217,7 +218,7 @@
         final CoordinateSystemAxis E = cs.getAxis(0);
         final CoordinateSystemAxis N = cs.getAxis(1);
         assertEquals("Easting, northing (E,N)", cs.getName().getCode());
-        assertEquals("Used in ProjectedCRS.", cs.getRemarks().toString());
+        assertRemarksEquals("Used in ProjectedCRS.", cs, null);
         assertEpsgIdentifierEquals("4400", getSingleton(cs.getIdentifiers()));
         assertEpsgIdentifierEquals("1",    getSingleton(E.getIdentifiers()));
         assertEpsgIdentifierEquals("2",    getSingleton(N.getIdentifiers()));
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/cs/DefaultEllipsoidalCSTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/cs/DefaultEllipsoidalCSTest.java
index 5139987..2e33bca 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/cs/DefaultEllipsoidalCSTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/cs/DefaultEllipsoidalCSTest.java
@@ -30,6 +30,7 @@
 import org.opengis.test.Validators;
 import org.apache.sis.xml.test.TestCase;
 import static org.apache.sis.referencing.Assertions.assertAxisEquals;
+import static org.apache.sis.referencing.Assertions.assertRemarksEquals;
 import static org.apache.sis.referencing.Assertions.assertEpsgIdentifierEquals;
 import static org.apache.sis.test.TestUtilities.getSingleton;
 
@@ -138,7 +139,7 @@
         final CoordinateSystemAxis φ = cs.getAxis(0);
         final CoordinateSystemAxis λ = cs.getAxis(1);
         assertEquals("Latitude (north), Longitude (east)",     cs.getName().getCode());
-        assertEquals("Used in two-dimensional GeographicCRS.", cs.getRemarks().toString());
+        assertRemarksEquals("Used in two-dimensional GeographicCRS.", cs, null);
         assertAxisEquals("Geodetic latitude",  "φ", AxisDirection.NORTH, -90,  +90, Units.DEGREE, RangeMeaning.EXACT, φ);
         assertAxisEquals("Geodetic longitude", "λ", AxisDirection.EAST, -180, +180, Units.DEGREE, RangeMeaning.WRAPAROUND, λ);
         assertEpsgIdentifierEquals("6422", getSingleton(cs.getIdentifiers()));
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/DefaultEllipsoidTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/DefaultEllipsoidTest.java
index 9a32294..e702cc4 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/DefaultEllipsoidTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/DefaultEllipsoidTest.java
@@ -25,6 +25,7 @@
 import static org.junit.jupiter.api.Assertions.*;
 import org.apache.sis.xml.test.TestCase;
 import static org.apache.sis.referencing.Assertions.assertWktEquals;
+import static org.apache.sis.referencing.Assertions.assertRemarksEquals;
 
 
 /**
@@ -129,7 +130,7 @@
     public void testEllipsoidXML() throws JAXBException {
         final DefaultEllipsoid ellipsoid = unmarshalFile(DefaultEllipsoid.class, openTestFile(false));
         assertEquals("Clarke 1880 (international foot)", ellipsoid.getName().getCode());
-        assertEquals("Definition in feet assumed to be international foot.", ellipsoid.getRemarks().toString());
+        assertRemarksEquals("Definition in feet assumed to be international foot.", ellipsoid, null);
         assertFalse (                    ellipsoid.isSphere());
         assertFalse (                    ellipsoid.isIvfDefinitive());
         assertEquals(20926202,           ellipsoid.getSemiMajorAxis());
@@ -153,7 +154,7 @@
     public void testSphereXML() throws JAXBException {
         final DefaultEllipsoid ellipsoid = unmarshalFile(DefaultEllipsoid.class, openTestFile(true));
         assertEquals("GRS 1980 Authalic Sphere", ellipsoid.getName().getCode());
-        assertEquals("Authalic sphere derived from GRS 1980 ellipsoid (code 7019).", ellipsoid.getRemarks().toString());
+        assertRemarksEquals("Authalic sphere derived from GRS 1980 ellipsoid (code 7019).", ellipsoid, null);
         assertTrue  (                          ellipsoid.isSphere());
         assertFalse (                          ellipsoid.isIvfDefinitive());
         assertEquals(6371007,                  ellipsoid.getSemiMajorAxis());
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/DefaultGeodeticDatumTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/DefaultGeodeticDatumTest.java
index b693b6c..c89b830 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/DefaultGeodeticDatumTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/DefaultGeodeticDatumTest.java
@@ -45,6 +45,7 @@
 import static org.apache.sis.test.Assertions.assertSerializedEquals;
 import static org.apache.sis.metadata.Assertions.assertXmlEquals;
 import static org.apache.sis.referencing.Assertions.assertWktEquals;
+import static org.apache.sis.referencing.Assertions.assertRemarksEquals;
 
 // Specific to the geoapi-3.1 and geoapi-4.0 branches:
 import static org.opengis.test.Assertions.assertMatrixEquals;
@@ -108,9 +109,9 @@
         assertEquals("This is a name",        datum.getName().getCode());
         assertEquals("This is a scope",       scope.toString(Locale.ROOT));
         assertEquals("Valide pour tel usage", scope.toString(Locale.FRENCH));
-        assertEquals("There is remarks",      datum.getRemarks().toString(Locale.ROOT));
-        assertEquals("Voici des remarques",   datum.getRemarks().toString(Locale.FRENCH));
-        assertEquals("注です。",                datum.getRemarks().toString(Locale.JAPANESE));
+        assertRemarksEquals("There is remarks",      datum, Locale.ROOT);
+        assertRemarksEquals("Voici des remarques",   datum, Locale.FRENCH);
+        assertRemarksEquals("注です。",              datum, Locale.JAPANESE);
     }
 
     /**
@@ -301,16 +302,12 @@
          * Values in the following tests are specific to our XML file.
          * The actual texts in the EPSG database are more descriptive.
          */
-        assertEquals("No distinction between the original and subsequent WGS 84 frames.",
-                datum.getRemarks().toString());
-        assertEquals("Satellite navigation.",
-                getScope(datum));
+        assertRemarksEquals("No distinction between the original and subsequent WGS 84 frames.", datum, null);
+        assertEquals("Satellite navigation.", getScope(datum));
         assertEquals("Station coordinates changed by a few centimetres in 1994, 1997, 2002 and 2012.",
-                datum.getAnchorDefinition().get().toString());
-        assertEquals(xmlDate("1984-01-01 00:00:00").toInstant(),
-                datum.getAnchorEpoch().orElse(null));
-        assertEquals("Defining parameters cited in EPSG database.",
-                datum.getEllipsoid().getRemarks().toString());
+                     datum.getAnchorDefinition().orElseThrow().toString());
+        assertEquals(xmlDate("1984-01-01 00:00:00").toInstant(), datum.getAnchorEpoch().orElseThrow());
+        assertRemarksEquals("Defining parameters cited in EPSG database.", datum.getEllipsoid(), null);
         return datum;
     }
 
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/DefaultPrimeMeridianTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/DefaultPrimeMeridianTest.java
index c7c77e6..d415906 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/DefaultPrimeMeridianTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/DefaultPrimeMeridianTest.java
@@ -37,6 +37,7 @@
 import org.apache.sis.xml.test.TestCase;
 import static org.apache.sis.metadata.Assertions.assertXmlEquals;
 import static org.apache.sis.referencing.Assertions.assertWktEquals;
+import static org.apache.sis.referencing.Assertions.assertRemarksEquals;
 
 
 /**
@@ -183,7 +184,7 @@
         final DefaultPrimeMeridian pm = unmarshalFile(DefaultPrimeMeridian.class, openTestFile(false));
         assertIsParis(pm);
         assertEquals(2.33722917, pm.getGreenwichLongitude(Units.DEGREE), 1E-12);
-        assertEquals("Equivalent to 2°20′14.025″.", pm.getRemarks().toString());
+        assertRemarksEquals("Equivalent to 2°20′14.025″.", pm, null);
         assertNull(pm.getName().getCodeSpace());
         assertWktEquals(Convention.WKT1,
                 "PRIMEM[“Paris”, 2.33722917, AUTHORITY[“EPSG”, “8903”]]", pm);
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 887c1f8..80821b8 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
@@ -30,6 +30,7 @@
 import org.apache.sis.xml.test.TestCase;
 import org.apache.sis.metadata.iso.citation.HardCodedCitations;
 import static org.apache.sis.referencing.Assertions.assertWktEquals;
+import static org.apache.sis.referencing.Assertions.assertRemarksEquals;
 import static org.apache.sis.test.TestUtilities.getSingleton;
 import static org.apache.sis.test.TestUtilities.getScope;
 
@@ -126,7 +127,7 @@
         assertIdentifierEquals("Apache Spatial Information System", "SIS", null, "MJ",
                                getSingleton(datum.getIdentifiers()), "identifier");
         assertEquals("Modified Julian", datum.getName().getCode());
-        assertEquals("Time measured as days since November 17, 1858 at 00:00 UTC.", datum.getRemarks().toString());
+        assertRemarksEquals("Time measured as days since November 17, 1858 at 00:00 UTC.", datum, null);
         assertEquals("History.", getScope(datum));
         assertEquals(new Date(ORIGIN), datum.getOrigin());
     }
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/DefaultVerticalDatumTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/DefaultVerticalDatumTest.java
index 50234ca..5a2241f 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/DefaultVerticalDatumTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/DefaultVerticalDatumTest.java
@@ -36,6 +36,7 @@
 import static org.apache.sis.test.TestUtilities.getScope;
 import static org.apache.sis.metadata.Assertions.assertXmlEquals;
 import static org.apache.sis.referencing.Assertions.assertWktEquals;
+import static org.apache.sis.referencing.Assertions.assertRemarksEquals;
 
 // Specific to the geoapi-3.1 and geoapi-4.0 branches:
 import org.opengis.referencing.datum.RealizationMethod;
@@ -98,7 +99,7 @@
          * Values in the following tests are specific to our XML file.
          * The actual texts in the EPSG database are more descriptive.
          */
-        assertEquals("Approximates geoid.",             datum.getRemarks().toString());
+        assertRemarksEquals("Approximates geoid.",      datum, null);
         assertEquals("Hydrography.",                    getScope(datum));
         assertEquals("Averaged over a 19-year period.", datum.getAnchorDefinition().get().toString());
         /*
@@ -131,8 +132,8 @@
          * those property does not have the same XML element name (SIS-160).
          * Below is all we have.
          */
-        assertEquals("Approximates geoid.", datum.getRemarks().toString());
-        assertEquals("Hydrography.",        getScope(datum));
+        assertRemarksEquals("Approximates geoid.", datum, null);
+        assertEquals("Hydrography.", getScope(datum));
         /*
          * Test marshalling. We cannot yet compare with the original XML file
          * because of all the information lost. This may be fixed in a future
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 9ced366..ebd448c 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");
-        assertNull  (c.getOperationVersion(), "operationVersion");
+        assertTrue  (c.getOperationVersion().isEmpty(), "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(), "operationVersion");
+        assertEquals("IGN-Fra", c.getOperationVersion().get(), "operationVersion");
 
         final OperationMethod method = c.getMethod();
         assertNotNull(method, "method");
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/ProvidersTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/ProvidersTest.java
index 54746ba..b4a1c3e 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/ProvidersTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/ProvidersTest.java
@@ -231,8 +231,8 @@
      */
     @Test
     public void testDescription() {
-        assertNotEquals(0, SatelliteTracking.SATELLITE_ORBIT_INCLINATION.getDescription().length());
-        assertNotEquals(0, SatelliteTracking.SATELLITE_ORBITAL_PERIOD   .getDescription().length());
-        assertNotEquals(0, SatelliteTracking.ASCENDING_NODE_PERIOD      .getDescription().length());
+        assertNotEquals(0, SatelliteTracking.SATELLITE_ORBIT_INCLINATION.getDescription().orElseThrow().length());
+        assertNotEquals(0, SatelliteTracking.SATELLITE_ORBITAL_PERIOD   .getDescription().orElseThrow().length());
+        assertNotEquals(0, SatelliteTracking.ASCENDING_NODE_PERIOD      .getDescription().orElseThrow().length());
     }
 }
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 8b04e27..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
@@ -31,6 +31,7 @@
 // Test dependencies
 import org.junit.jupiter.api.Test;
 import static org.junit.jupiter.api.Assertions.*;
+import static org.apache.sis.referencing.Assertions.assertRemarksEquals;
 import org.apache.sis.xml.test.TestCase;
 
 
@@ -72,7 +73,7 @@
          */
         assertEquals(name, p.getName().getCode());
         assertEquals(remarks, (remarks == null) ? null : p.getRemarks().toString());
-        assertNull(p.getDescription());
+        assertTrue(p.getDescription().isEmpty());
         assertNull(p.getValueClass());
         assertEquals(0, p.getMinimumOccurs());
         assertEquals(1, p.getMaximumOccurs());
@@ -155,7 +156,7 @@
         assertEquals (0,                     merged.getMinimumOccurs());
         assertEquals (1,                     merged.getMaximumOccurs());
         assertEquals (Integer.class,         merged.getValueClass());
-        assertSame   (provided.getRemarks(), merged.getRemarks());
+        assertEquals (provided.getRemarks(), merged.getRemarks());
         loggings.assertNoUnexpectedLog();
     }
 
@@ -169,13 +170,13 @@
     public void testGroupSubstitution() throws JAXBException {
         final Map<String,String> properties = new HashMap<>(4);
         assertNull(properties.put(DefaultParameterDescriptor.NAME_KEY, "Group"));
-        final ParameterDescriptorGroup provided = new DefaultParameterDescriptorGroup(properties, 1, 2,
+        final var provided = new DefaultParameterDescriptorGroup(properties, 1, 2,
                 unmarshal("Parameter A", null),
                 unmarshal("Parameter B", "Remarks B."),
                 unmarshal("Parameter C", null));
 
         assertNull(properties.put(DefaultParameterDescriptor.REMARKS_KEY, "More details here."));
-        final ParameterDescriptorGroup complete = new DefaultParameterDescriptorGroup(properties, 1, 2,
+        final var complete = new DefaultParameterDescriptorGroup(properties, 1, 2,
                 create("Parameter A", "Remarks A.", false, 3),
                 create("Parameter B", "Remarks B.", false, 4),
                 create("Parameter C", "Remarks C.", false, 5),
@@ -196,23 +197,22 @@
     public void testGroupMergeBecauseDifferentProperties() throws JAXBException {
         final Map<String,String> properties = new HashMap<>(4);
         assertNull(properties.put(DefaultParameterDescriptor.NAME_KEY, "Group"));
-        final ParameterDescriptorGroup provided = new DefaultParameterDescriptorGroup(properties, 1, 2,
+        final var provided = new DefaultParameterDescriptorGroup(properties, 1, 2,
                 unmarshal("Parameter A", "Remarks A."),
                 unmarshal("Parameter B", "Remarks B."),
                 unmarshal("Parameter C", "Remarks C."));
 
         assertNull(properties.put(DefaultParameterDescriptor.REMARKS_KEY, "More details here."));
-        final ParameterDescriptorGroup complete = new DefaultParameterDescriptorGroup(properties, 1, 2,
+        final var complete = new DefaultParameterDescriptorGroup(properties, 1, 2,
                 create("Parameter A", "Remarks A.", true,  3),
                 create("Parameter B", "Remarks B.", false, 4),
                 create("Parameter C", "Different.", false, 5),
                 create("Parameter D", "Remarks D.", false, 6));
 
-        final ParameterDescriptorGroup merged =
-                (ParameterDescriptorGroup) CC_GeneralOperationParameter.merge(provided, complete);
+        final var merged = (ParameterDescriptorGroup) CC_GeneralOperationParameter.merge(provided, complete);
         assertNotSame(complete, provided);
         assertSame   (complete.getName(),    merged.getName());
-        assertSame   (complete.getRemarks(), merged.getRemarks());
+        assertEquals (complete.getRemarks(), merged.getRemarks());
         assertEquals (1,                     merged.getMinimumOccurs());
         assertEquals (2,                     merged.getMaximumOccurs());
 
@@ -237,21 +237,20 @@
     public void testGroupMergeBecauseMissingParameter() throws JAXBException {
         final Map<String,String> properties = new HashMap<>(4);
         assertNull(properties.put(DefaultParameterDescriptor.NAME_KEY, "Group"));
-        final ParameterDescriptorGroup provided = new DefaultParameterDescriptorGroup(properties, 1, 2,
+        final var provided = new DefaultParameterDescriptorGroup(properties, 1, 2,
                 unmarshal("Parameter A", null),
                 unmarshal("Parameter C", null));
 
         assertNull(properties.put(DefaultParameterDescriptor.REMARKS_KEY, "More details here."));
-        final ParameterDescriptorGroup complete = new DefaultParameterDescriptorGroup(properties, 1, 2,
+        final var complete = new DefaultParameterDescriptorGroup(properties, 1, 2,
                 create("Parameter A", null, false, 3),
                 create("Parameter B", null, true,  4),
                 create("Parameter C", null, false, 5));
 
-        final ParameterDescriptorGroup merged =
-                (ParameterDescriptorGroup) CC_GeneralOperationParameter.merge(provided, complete);
+        final var merged = (ParameterDescriptorGroup) CC_GeneralOperationParameter.merge(provided, complete);
         assertNotSame(complete, provided);
         assertSame   (complete.getName(),    merged.getName());
-        assertSame   (complete.getRemarks(), merged.getRemarks());
+        assertEquals (complete.getRemarks(), merged.getRemarks());
         assertEquals (1,                     merged.getMinimumOccurs());
         assertEquals (2,                     merged.getMaximumOccurs());
 
@@ -279,22 +278,21 @@
     public void testGroupMergeBecauseExtraParameter() throws JAXBException {
         final Map<String,String> properties = new HashMap<>(4);
         assertNull(properties.put(DefaultParameterDescriptor.NAME_KEY, "Group"));
-        final ParameterDescriptorGroup provided = new DefaultParameterDescriptorGroup(properties, 1, 2,
+        final var provided = new DefaultParameterDescriptorGroup(properties, 1, 2,
                 unmarshal("Parameter A", "Remarks A."),
                 unmarshal("Parameter B", "Remarks B."),
                 unmarshal("Parameter C", "Remarks C."));
 
         assertNull(properties.put(DefaultParameterDescriptor.REMARKS_KEY, "More details here."));
-        final ParameterDescriptorGroup complete = new DefaultParameterDescriptorGroup(properties, 1, 2,
+        final var complete = new DefaultParameterDescriptorGroup(properties, 1, 2,
                 create("Parameter A", "Remarks A.", false, 3),
                 create("Parameter C", "Remarks C.", false, 4));
 
         loggings.assertNoUnexpectedLog();
-        final ParameterDescriptorGroup merged =
-                (ParameterDescriptorGroup) CC_GeneralOperationParameter.merge(provided, complete);
+        final var merged = (ParameterDescriptorGroup) CC_GeneralOperationParameter.merge(provided, complete);
         assertNotSame(complete, provided);
         assertSame   (complete.getName(),    merged.getName());
-        assertSame   (complete.getRemarks(), merged.getRemarks());
+        assertEquals (complete.getRemarks(), merged.getRemarks());
         assertEquals (1,                     merged.getMinimumOccurs());
         assertEquals (2,                     merged.getMaximumOccurs());
         loggings.assertNextLogContains("Parameter B", "Group");
@@ -306,7 +304,7 @@
 
         final GeneralParameterDescriptor extra = itm.next();
         assertEquals("Parameter B", extra.getName().getCode());
-        assertEquals("Remarks B.",  extra.getRemarks().toString());
+        assertRemarksEquals("Remarks B.", extra, null);
 
         verifyParameter(itc.next(), itm.next(), true, "Remarks C.");
         assertFalse(itc.hasNext());
@@ -326,6 +324,6 @@
         assertEquals(0,                  merged.getMinimumOccurs());
         assertEquals(1,                  merged.getMaximumOccurs());
         assertEquals(Integer.class,      ((ParameterDescriptor<?>) merged).getValueClass());
-        assertEquals(remarks,            (remarks == null) ? null : merged.getRemarks().toString());
+        assertRemarksEquals(remarks,     merged, null);
     }
 }
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 b03153c..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
@@ -155,7 +155,7 @@
         assertNotSame(incomplete, complete, "Latitude of natural origin");
         assertNotSame(fromValue,  complete, "Latitude of natural origin");
         assertSame   (fromValue .getName(),       complete.getName());
-        assertSame   (incomplete.getRemarks(),    complete.getRemarks());
+        assertEquals (incomplete.getRemarks(),    complete.getRemarks());
         assertEquals (Double.class,               complete.getValueClass());
         assertSame   (fromValue.getValueDomain(), complete.getValueDomain());
         /*
@@ -221,7 +221,7 @@
         if (remarks != null) {
             assertEquals(remarks, actual.getRemarks().toString());
         } else {
-            assertSame(expected.getRemarks(), actual.getRemarks());
+            assertEquals(expected.getRemarks(), actual.getRemarks());
         }
     }
 }
diff --git a/endorsed/src/org.apache.sis.storage.xml/test/org/apache/sis/storage/gpx/TypesTest.java b/endorsed/src/org.apache.sis.storage.xml/test/org/apache/sis/storage/gpx/TypesTest.java
index 5408ff3..589e94d 100644
--- a/endorsed/src/org.apache.sis.storage.xml/test/org/apache/sis/storage/gpx/TypesTest.java
+++ b/endorsed/src/org.apache.sis.storage.xml/test/org/apache/sis/storage/gpx/TypesTest.java
@@ -67,7 +67,7 @@
             final GenericName name = p.getName();
             if (!AttributeConvention.contains(name)) {
                 final String label = name.toString();
-                assertNonEmpty(label, p.getDesignation());
+                assertNonEmpty(label, p.getDesignation().orElse(null));
                 assertNonEmpty(label, p.getDefinition());
             }
         }
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/folder/StoreProvider.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/folder/StoreProvider.java
index 24412ac..dd72fe7 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/folder/StoreProvider.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/folder/StoreProvider.java
@@ -117,7 +117,7 @@
      * Creates a parameter descriptor equals to the given one except for the remarks which are set to the given value.
      */
     private static <T> ParameterDescriptor<T> annotate(ParameterBuilder builder, ParameterDescriptor<T> e, InternationalString remark) {
-        return builder.addName(e.getName()).setDescription(e.getDescription()).setRemarks(remark).create(e.getValueClass(), null);
+        return builder.addName(e.getName()).setDescription(e.getDescription().orElse(null)).setRemarks(remark).create(e.getValueClass(), null);
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/Classes.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/Classes.java
index 1c86c7a..c14ee67 100644
--- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/Classes.java
+++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/Classes.java
@@ -23,6 +23,7 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.LinkedHashSet;
+import java.util.Optional;
 import java.lang.reflect.Type;
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
@@ -170,6 +171,8 @@
      * @param  field  the field for which to obtain the parameterized type.
      * @return the upper bound of parameterized type, or {@code null} if the given field
      *         is not of a parameterized type.
+     *
+     * @see #isParameterizedProperty(Class)
      */
     public static Class<?> boundOfParameterizedProperty(final Field field) {
         return getActualTypeArgument(field.getGenericType());
@@ -190,6 +193,8 @@
      * @param  method  the getter or setter method for which to obtain the parameterized type.
      * @return the upper bound of parameterized type, or {@code null} if the given method
      *         is not a getter or setter for a property of a parameterized type.
+     *
+     * @see #isParameterizedProperty(Class)
      */
     public static Class<?> boundOfParameterizedProperty(final Method method) {
         final Type[] parameters = method.getGenericParameterTypes();
@@ -859,6 +864,31 @@
     }
 
     /**
+     * Returns whether the actual type of a property is the parameterized type according SIS.
+     * The given {@code type} argument should be the type of a field or the return type of a
+     * {@linkplain #isPossibleGetter(Method) getter method}. If this method returns {@code true},
+     * then a {@code boundOfParameterizedProperty(…)} method needs to be invoked in order to get
+     * the actual property type.
+     *
+     * <p>The current implementation tests only if the given type is {@link Optional}.
+     * More types may be added in future Apache SIS versions, depending on API evolutions.
+     * Note that collections are intentionally <em>not</em> recognized by this method,
+     * because they usually need to be handled in a special way by the caller.</p>
+     *
+     * @param  type  the field type or getter method return type to test.
+     * @return whether a {@code boundOfParameterizedProperty(…)} method need to be invoked
+     *         for getting the actual property type.
+     *
+     * @see #boundOfParameterizedProperty(Field)
+     * @see #boundOfParameterizedProperty(Method)
+     *
+     * @since 1.5
+     */
+    public static boolean isParameterizedProperty(final Class<?> type) {
+        return type == Optional.class;
+    }
+
+    /**
      * Returns {@code true} if the given class is non-null, public and exported.
      *
      * @param  type  the class to test, or {@code null}.
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 197f995..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
@@ -41,7 +41,7 @@
  * </ul>
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.3
+ * @version 1.5
  * @since   0.3
  */
 public interface Deprecable {
@@ -62,5 +62,7 @@
      * @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}.
      */
-    InternationalString getRemarks();
+    default InternationalString getRemarks() {
+        return null;
+    }
 }
diff --git a/geoapi/snapshot b/geoapi/snapshot
index 6eb8f73..725c585 160000
--- a/geoapi/snapshot
+++ b/geoapi/snapshot
@@ -1 +1 @@
-Subproject commit 6eb8f736dc3ea0c927f580d4863cbc19540641aa
+Subproject commit 725c585a097b2d169d5bb022c2d960961685bfd4
diff --git a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/dataset/FeatureTable.java b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/dataset/FeatureTable.java
index a429e4c..29314d5 100644
--- a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/dataset/FeatureTable.java
+++ b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/dataset/FeatureTable.java
@@ -282,7 +282,7 @@
              */
             final GenericName qualifiedName = pt.getName();
             final String name = qualifiedName.toString();
-            String title = string(pt.getDesignation());
+            String title = string(pt.getDesignation().orElse(null));
             if (title == null) {
                 title = string(qualifiedName.toInternationalString());
                 if (title == null) title = name;