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

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


The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
     new 32d24eaa26 Restore the deferred object creation in 
`IdentifiedObjectFinder` and the omission of database scanning when a match is 
found early.
32d24eaa26 is described below

commit 32d24eaa261ddc9a06210d561dcd5c47a406db17
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Fri Aug 29 15:21:16 2025 +0200

    Restore the deferred object creation in `IdentifiedObjectFinder`
    and the omission of database scanning when a match is found early.
---
 .../apache/sis/referencing/AuthorityFactories.java |  19 +-
 .../apache/sis/referencing/IdentifiedObjects.java  |   9 +-
 .../factory/AuthorityFactoryIdentifier.java        |  19 +-
 .../factory/IdentifiedObjectFinder.java            | 554 ++++++++++++++-------
 .../factory/MultiAuthoritiesFactory.java           | 106 +++-
 .../referencing/factory/sql/EPSGCodeFinder.java    |  31 +-
 .../referencing/factory/sql/EPSGDataAccess.java    |   9 +-
 .../sis/referencing/factory/sql/TableInfo.java     |   4 +-
 .../referencing/internal/EPSGParameterDomain.java  |   4 +-
 .../sis/referencing/internal/MergedProperties.java |   4 +-
 .../operation/CoordinateOperationRegistry.java     |   2 +
 .../sis/referencing/privy/FilteredIterator.java    | 113 +++++
 .../org/apache/sis/referencing/privy/LazySet.java  |  25 +-
 .../factory/ConcurrentAuthorityFactoryTest.java    |   2 +-
 .../factory/IdentifiedObjectFinderTest.java        |   3 +-
 .../referencing/factory/sql/EPSGFactoryTest.java   |  24 +-
 .../sis/storage/sql/feature/InfoStatements.java    |   8 +-
 .../apache/sis/util/privy/SetOfUnknownSize.java    |   6 +-
 netbeans-project/nbproject/project.xml             |   1 +
 .../apache/sis/resources/embedded/Generator.java   |   2 +-
 20 files changed, 671 insertions(+), 274 deletions(-)

diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/AuthorityFactories.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/AuthorityFactories.java
index c20937e0a6..287bd3fadb 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/AuthorityFactories.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/AuthorityFactories.java
@@ -42,6 +42,7 @@ import 
org.apache.sis.referencing.factory.GeodeticAuthorityFactory;
 import org.apache.sis.referencing.factory.IdentifiedObjectFinder;
 import org.apache.sis.referencing.factory.UnavailableFactoryException;
 import org.apache.sis.referencing.factory.sql.EPSGFactory;
+import org.apache.sis.referencing.privy.FilteredIterator;
 import org.apache.sis.util.logging.Logging;
 
 
@@ -121,7 +122,9 @@ final class AuthorityFactories<T extends AuthorityFactory> 
extends LazySet<T> {
             Reflect.log(AuthorityFactories.class, "createSourceIterator", e);
             loader = ServiceLoader.load(service);
         }
-        return loader.iterator();
+        // Excludes the `EPSGFactoryProxy` instance.
+        return new FilteredIterator<>(loader.iterator(),
+                (element) -> (element instanceof EPSGFactoryProxy) ? null : 
element);
     }
 
     /**
@@ -137,20 +140,6 @@ final class AuthorityFactories<T extends AuthorityFactory> 
extends LazySet<T> {
         return (T[]) new GeodeticAuthorityFactory[] {getEPSG(true)};
     }
 
-    /**
-     * Invoked by {@link LazySet} for fetching the next element from the given 
iterator.
-     * Skips the {@link EPSGFactoryProxy} if possible, or returns {@code null} 
otherwise.
-     * Note that {@link MultiAuthoritiesFactory} is safe to null values.
-     */
-    @Override
-    protected T next(final Iterator<? extends T> it) {
-        T e = it.next();
-        if (e instanceof EPSGFactoryProxy) {
-            e = it.hasNext() ? it.next() : null;
-        }
-        return e;
-    }
-
     /**
      * Sets the EPSG factory to the given value.
      *
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/IdentifiedObjects.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/IdentifiedObjects.java
index 6ea0d83d3d..9822787bbf 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/IdentifiedObjects.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/IdentifiedObjects.java
@@ -46,6 +46,7 @@ import org.apache.sis.util.privy.Strings;
 import org.apache.sis.util.privy.Constants;
 import org.apache.sis.util.privy.DefinitionURI;
 import org.apache.sis.util.privy.CollectionsExt;
+import org.apache.sis.util.collection.BackingStoreException;
 import org.apache.sis.pending.jdk.JDK21;
 import org.apache.sis.xml.IdentifierSpace;
 import org.apache.sis.metadata.privy.Identifiers;
@@ -582,7 +583,7 @@ public final class IdentifiedObjects extends Static {
                                     final IdentifiedObjectFinder finder) 
throws FactoryException
     {
         String urn = null;
-        if (object != null) {
+        if (object != null) try {
             for (final IdentifiedObject candidate : finder.find(object)) {
                 String c = toURN(candidate.getClass(), 
getIdentifier(candidate, authority));
                 if (c == null && authority == null) {
@@ -606,6 +607,8 @@ public final class IdentifiedObjects extends Static {
                     urn = c;
                 }
             }
+        } catch (BackingStoreException e) {
+            throw e.unwrapOrRethrow(FactoryException.class);
         }
         return urn;
     }
@@ -642,7 +645,7 @@ public final class IdentifiedObjects extends Static {
     @OptionalCandidate
     public static Integer lookupEPSG(final IdentifiedObject object) throws 
FactoryException {
         Integer code = null;
-        if (object != null) {
+        if (object != null) try {
             for (final IdentifiedObject candidate : 
newFinder(Constants.EPSG).find(object)) {
                 final Identifier id = getIdentifier(candidate, Citations.EPSG);
                 if (id != null) try {
@@ -655,6 +658,8 @@ public final class IdentifiedObjects extends Static {
                     warning("lookupEPSG", e);
                 }
             }
+        } catch (BackingStoreException e) {
+            throw e.unwrapOrRethrow(FactoryException.class);
         }
         return code;
     }
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/AuthorityFactoryIdentifier.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/AuthorityFactoryIdentifier.java
index a67a3e1d15..4b5e51a376 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/AuthorityFactoryIdentifier.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/AuthorityFactoryIdentifier.java
@@ -106,7 +106,7 @@ final class AuthorityFactoryIdentifier {
     }
 
     /**
-     * The type of the factory needed.
+     * The type of the factory which is needed.
      */
     final Type type;
 
@@ -123,7 +123,7 @@ final class AuthorityFactoryIdentifier {
     private String authority;
 
     /**
-     * The version part of a URI, or {@code null} if none.
+     * The version part of a <abbr>URI</abbr>, or {@code null} if none.
      * If the version contains alphabetic characters, they should be in lower 
case.
      *
      * <h4>Example</h4>
@@ -139,6 +139,10 @@ final class AuthorityFactoryIdentifier {
      * Creates a new identifier for a factory of the given type, authority and 
version.
      * The given authority shall be already in upper cases and the version in 
lower cases
      * (this is not verified by this constructor).
+     *
+     * @param  type       the type of the factory which is needed.
+     * @param  authority  the authority of the factory, in upper case.
+     * @param  version    the version part of a <abbr>URI</abbr>, or {@code 
null} if none.
      */
     private AuthorityFactoryIdentifier(final Type type, final String 
authority, final String version) {
         this.type      = type;
@@ -164,6 +168,11 @@ final class AuthorityFactoryIdentifier {
     /**
      * Creates a new identifier for a factory of the given type, authority and 
version.
      * Only the version can be null.
+     *
+     * @param  type       the type of the factory which is needed.
+     * @param  authority  the authority of the factory, case-insensitive.
+     * @param  version    the version part of a <abbr>URI</abbr>, or {@code 
null} if none.
+     * @return identifier for a factory of the given type, authority and 
version.
      */
     static AuthorityFactoryIdentifier create(final Type type, final String 
authority, final String version) {
         return new AuthorityFactoryIdentifier(type, 
authority.toUpperCase(IDENTIFIER_LOCALE),
@@ -173,6 +182,9 @@ final class AuthorityFactoryIdentifier {
     /**
      * Returns an identifier for a factory of the same type as this identifier,
      * but a different authority and no version.
+     *
+     * @param  newAuthority  the authority of the factory, case-insensitive.
+     * @return identifier for a factory of the given authority.
      */
     AuthorityFactoryIdentifier unversioned(final String newAuthority) {
         if (version == null && newAuthority.equals(authority)) {
@@ -201,6 +213,9 @@ final class AuthorityFactoryIdentifier {
 
     /**
      * Creates a new identifier for the same authority and version than this 
identifier, but a different factory.
+     *
+     * @param  type       the type of the factory which is needed.
+     * @return identifier for a factory of the given type.
      */
     AuthorityFactoryIdentifier newType(final Type newType) {
         return new AuthorityFactoryIdentifier(newType, authority, version);
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/IdentifiedObjectFinder.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/IdentifiedObjectFinder.java
index a5dfbcd4a5..6d950d6db6 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/IdentifiedObjectFinder.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/IdentifiedObjectFinder.java
@@ -16,9 +16,11 @@
  */
 package org.apache.sis.referencing.factory;
 
+import java.util.Iterator;
 import java.util.Set;
-import java.util.LinkedHashSet;
+import java.util.HashSet;
 import java.util.Objects;
+import java.util.function.Function;
 import java.util.logging.Level;
 import java.util.logging.LogRecord;
 import org.opengis.util.FactoryException;
@@ -27,10 +29,12 @@ import org.opengis.referencing.IdentifiedObject;
 import org.opengis.referencing.AuthorityFactory;
 import org.opengis.referencing.NoSuchAuthorityCodeException;
 import org.opengis.referencing.datum.Datum;
-import org.apache.sis.referencing.AbstractIdentifiedObject;
 import org.apache.sis.referencing.IdentifiedObjects;
 import org.apache.sis.referencing.datum.DatumOrEnsemble;
+import org.apache.sis.referencing.privy.FilteredIterator;
+import org.apache.sis.referencing.privy.LazySet;
 import org.apache.sis.util.ComparisonMode;
+import org.apache.sis.util.Disposable;
 import org.apache.sis.util.Utilities;
 import org.apache.sis.util.privy.Constants;
 import org.apache.sis.util.collection.BackingStoreException;
@@ -43,7 +47,7 @@ import org.opengis.referencing.datum.DatumEnsemble;
 
 /**
  * Searches in an authority factory for objects approximately equal to a given 
object.
- * This class can be used for fetching a fully defined {@linkplain 
AbstractIdentifiedObject identified object}
+ * This class can be used for fetching a fully defined {@linkplain 
IdentifiedObject identified object}
  * from an incomplete one, for example from an object without "{@code ID[…]}" 
or "{@code AUTHORITY[…]}"
  * element in <i>Well Known Text</i>.
  *
@@ -70,7 +74,7 @@ import org.opengis.referencing.datum.DatumEnsemble;
  */
 public class IdentifiedObjectFinder {
     /**
-     * The domain of the search (for example whether to include deprecated 
objects in the search).
+     * The domain of the search (for example, whether to include deprecated 
objects in the search).
      *
      * @author  Martin Desruisseaux (Geomatys)
      * @version 1.2
@@ -81,53 +85,56 @@ public class IdentifiedObjectFinder {
      */
     public enum Domain {
         /**
-         * Fast lookup based only on embedded identifiers and names. If those 
identification information
-         * does not allow to locate an object in the factory, then the search 
will return an empty set.
+         * Fast lookup based only on declared identifiers.
+         * If those identification information does not allow to locate an 
object in the factory,
+         * then the {@code find(…)} method will return an empty set instead of 
performing
+         * an exhaustive search in the geodetic dataset.
          *
          * <h4>Example</h4>
-         * If {@link IdentifiedObjectFinder#find(IdentifiedObject)} is invoked 
with an object having the {@code "4326"}
-         * {@linkplain AbstractIdentifiedObject#getIdentifiers() identifier}, 
then the {@code find(…)} method will invoke
+         * If {@link #find(IdentifiedObject)} is invoked with an object having 
the {@code "4326"}
+         * {@linkplain IdentifiedObject#getIdentifiers() identifier}, then the 
{@code find(…)} method will invoke
          * <code>factory.{@linkplain 
GeodeticAuthorityFactory#createGeographicCRS(String) 
createGeographicCRS}("4326")</code>
-         * and compare the object from the factory with the object to search.
-         * If the objects do not match, then another attempt will be done 
using the
-         * {@linkplain AbstractIdentifiedObject#getName() object name}. If 
using name does not work neither,
+         * and compare the result with the object to search.
+         * If the two objects do not match, then some implementations may 
perform another attempt using the
+         * {@linkplain IdentifiedObject#getName() object name}. If using the 
name does not work neither,
          * then {@code find(…)} method makes no other attempt and returns an 
empty set.
          */
         DECLARATION,
 
         /**
-         * Lookup based on valid (non-deprecated) objects known to the factory.
-         * First, a fast lookup is performed based on {@link #DECLARATION}.
-         * If the fast lookup gave no result, then a more extensive search is 
performed by scanning the content
-         * of the dataset.
+         * Lookup based on declared identifiers and on non-deprecated objects 
known to the factory.
+         * First, a fast lookup is performed as described in {@link 
#DECLARATION}.
+         * If the last lookup found some matches, those matches are returned 
without scanning the rest of the database.
+         * It may be an incomplete set compared to what {@link 
#EXHAUSTIVE_VALID_DATASET} would have returned.
+         * If the fast lookup gave no result, only then an exhaustive search 
is performed by scanning
+         * the content of the geodetic dataset.
          *
-         * <h4>Example</h4>
-         * If {@link IdentifiedObjectFinder#find(IdentifiedObject)} is invoked 
with an object equivalent to the
-         * {@linkplain org.apache.sis.referencing.CommonCRS#WGS84 WGS84} 
geographic CRS but does not declare the
-         * {@code "4326"} identifier and does not have the <q>WGS 84</q> name, 
then the search based on
-         * {@link #DECLARATION} will give no result. The {@code find(…)} 
method will then scan the dataset for
-         * geographic CRS using equivalent datum and coordinate system. This 
may be a costly operation.
+         * <p>This is the default domain of {@link IdentifiedObjectFinder}.</p>
          *
-         * This is the default domain of {@link IdentifiedObjectFinder}.
+         * <h4>Example</h4>
+         * If {@link #find(IdentifiedObject)} is invoked with an object 
equivalent to the
+         * {@linkplain org.apache.sis.referencing.CommonCRS#WGS84 WGS84} 
geographic <abbr>CRS</abbr>
+         * but without declaring the {@code "4326"} identifier and without the 
<q>WGS 84</q> name,
+         * then the initial lookup described in {@link #DECLARATION} will give 
no result.
+         * As a fallback, the {@code find(…)} method scans the geodetic 
dataset in search
+         * for geographic <abbr>CRS</abbr> equivalent to the specified object.
+         * It may be a costly operation.
          */
         VALID_DATASET,
 
         /**
-         * Lookup unconditionally based on all valid (non-deprecated) objects 
known to the factory.
+         * Search unconditionally based on all valid (non-deprecated) objects 
known to the factory.
          * This is similar to {@link #VALID_DATASET} except that the fast 
{@link #DECLARATION} lookup is skipped.
-         * Instead, a potentially costly scan of the database is 
unconditionally performed
+         * Instead, a potentially costly scan of the geodetic dataset is 
unconditionally performed
          * (unless the result is already in the cache).
          *
-         * <p>This domain can be useful when the search {@linkplain 
#isIgnoringAxes() ignores axis order}.
-         * If axis order is <em>not</em> ignored, then this domain usually has 
no advantage over {@link #VALID_DATASET}
-         * (unless the geodetic dataset contains duplicated entries) to 
justify the performance cost.</p>
-         *
          * <h4>Use case</h4>
-         * The EPSG database sometimes contains two definitions for almost 
identical geographic CRS,
-         * one with (<var>latitude</var>, <var>longitude</var>) axis order and 
one with reverse order
-         * (e.g. EPSG::4171 versus EPSG::7084). It is sometimes useful to know 
all variants of a given CRS.
-         * The {@link #VALID_DATASET} domain may not give a complete set 
because the "fast lookup by identifier"
-         * optimization may prevent {@link IdentifiedObjectFinder} to scan the 
rest of the database.
+         * The <abbr>EPSG</abbr> geodetic dataset sometimes contains two 
definitions for almost identical
+         * geographic <abbr>CRS</abbr>, one with (<var>latitude</var>, 
<var>longitude</var>) axis order
+         * and one with reverse order (e.g. EPSG::4171 versus EPSG::7084). It 
is sometimes useful to know
+         * all variants of a given <abbr>CRS</abbr> in a search {@linkplain 
#isIgnoringAxes() ignoring axis order}.
+         * The {@link #VALID_DATASET} domain may not give a complete set 
because the "fast lookup by identifiers"
+         * optimization may prevent {@link IdentifiedObjectFinder} to scan the 
rest of the geodetic dataset.
          * This {@code EXHAUSTIVE_VALID_DATASET} domain forces such scan.
          *
          * @since 1.2
@@ -142,12 +149,6 @@ public class IdentifiedObjectFinder {
         ALL_DATASET
     }
 
-    /**
-     * The criterion for determining if a candidate found by {@code 
IdentifiedObjectFinder}
-     * should be considered equals to the requested object.
-     */
-    static final ComparisonMode COMPARISON_MODE = ComparisonMode.APPROXIMATE;
-
     /**
      * The factory to use for creating objects. This is the factory specified 
at construction time.
      */
@@ -183,7 +184,7 @@ public class IdentifiedObjectFinder {
     /**
      * Creates a finder using the specified factory.
      *
-     * <h4>API note</h4>
+     * <h4><abbr>API</abbr> note</h4>
      * This constructor is protected because instances of this class should 
not be created directly.
      * Use {@link GeodeticAuthorityFactory#newIdentifiedObjectFinder()} 
instead.
      *
@@ -202,7 +203,7 @@ public class IdentifiedObjectFinder {
      * An example of wrapper is {@link ConcurrentAuthorityFactory}'s finder.
      *
      * <p>This method also copies the configuration of the given finder, thus 
providing a central place
-     * where to add calls to setters methods if such methods are added in a 
future SIS version.</p>
+     * where to add calls to setter methods if such methods are added in a 
future <abbr>SIS</abbr> version.</p>
      *
      * @param  other  the cache or the adapter wrapping this finder.
      */
@@ -213,9 +214,10 @@ public class IdentifiedObjectFinder {
     }
 
     /**
-     * Returns the domain of the search (for example whether to include 
deprecated objects in the search).
-     * If {@code DECLARATION}, only a fast lookup based on embedded 
identifiers and names will be performed.
-     * Otherwise an exhaustive full scan against all registered objects will 
be performed (may be slow).
+     * Returns the domain of the search (for example, whether to include 
deprecated objects in the search).
+     * If the domain is {@code DECLARATION}, then the {@code find(…)} method 
will only perform a fast lookup
+     * based on the identifiers and the names of the object to search.
+     * Otherwise, an exhaustive scan of the geodetic dataset will be performed 
(may be slow).
      *
      * <p>The default value is {@link Domain#VALID_DATASET}.</p>
      *
@@ -226,8 +228,8 @@ public class IdentifiedObjectFinder {
     }
 
     /**
-     * Sets the domain of the search (for example whether to include 
deprecated objects in the search).
-     * If this method is never invoked, then the default value is {@link 
Domain#VALID_DATASET}.
+     * Sets the domain of the search (for example, whether to include 
deprecated objects in the search).
+     * If this method is never invoked, the default value is {@link 
Domain#VALID_DATASET}.
      *
      * @param  domain  the domain of the search.
      */
@@ -259,13 +261,33 @@ public class IdentifiedObjectFinder {
     }
 
     /**
-     * Returns {@code true} if a candidate found by {@code 
IdentifiedObjectFinder} should be considered equals to the
-     * requested object. This method invokes the {@code equals(…)} method on 
the {@code candidate} argument instead
-     * than on the user-specified {@code object} on the assumption that 
implementations coming from the factory are
-     * more reliable than user-specified objects.
+     * Returns the comparison mode to use when comparing a candidate against 
the object to search.
+     */
+    private ComparisonMode getComparisonMode() {
+        return ignoreAxes ? ComparisonMode.ALLOW_VARIANT : 
ComparisonMode.APPROXIMATE;
+    }
+
+    /**
+     * Returns {@code true} if a candidate created by a factory should be 
considered equal to the object to search.
+     * The {@code mode} and {@code proxy} arguments may be snapshots of the 
{@code IdentifiedObjectFinder}'s state
+     * taken at the time when the {@link Instances} iterable has been created.
+     *
+     * <h4>Implementation note</h4>
+     * This method invokes the {@code equals(…)} method on the {@code 
candidate} argument instead of {@code object}
+     * specified by the user on the assumption that implementations coming 
from the factory are more reliable than
+     * user-specified objects.
+     *
+     * @param  candidate  an object created by an authority factory.
+     * @param  object     the user-specified object to search.
+     * @param  mode       value of {@link #getComparisonMode()} (may be a 
snapshot).
+     * @param  proxy      value of {@link #proxy} (may be a snapshot).
+     * @return whether the given candidate can be considered equal to the 
object to search.
+     *
+     * @see #createAndFilter(AuthorityFactory, String, IdentifiedObject)
      */
-    private boolean match(final IdentifiedObject candidate, final 
IdentifiedObject object) {
-        final ComparisonMode mode = ignoreAxes ? ComparisonMode.ALLOW_VARIANT 
: COMPARISON_MODE;
+    private static boolean match(final IdentifiedObject candidate, final 
IdentifiedObject object,
+                                 final ComparisonMode mode, final 
AuthorityFactoryProxy<?> proxy)
+    {
         if (Utilities.deepEquals(candidate, object, mode)) {
             return true;
         }
@@ -302,53 +324,6 @@ public class IdentifiedObjectFinder {
         return result;
     }
 
-    /**
-     * Lookups objects which are approximately equal to the specified object.
-     * This method tries to instantiate objects identified by the {@linkplain 
#getCodeCandidates set of candidate codes}
-     * with the authority factory specified at construction time.
-     * The created objects which are equal to the specified object in the
-     * the sense of {@link ComparisonMode#APPROXIMATE} are returned.
-     *
-     * @param  object  the object looked up.
-     * @return the identified objects, or an empty set if not found.
-     * @throws FactoryException if an error occurred while creating an object.
-     */
-    public Set<IdentifiedObject> find(final IdentifiedObject object) throws 
FactoryException {
-        Set<IdentifiedObject> result = 
getFromCache(Objects.requireNonNull(object));
-        if (result == null) {
-            final AuthorityFactoryProxy<?> previousProxy = proxy;
-            proxy = AuthorityFactoryProxy.getInstance(object.getClass());
-            try {
-                if (domain != Domain.EXHAUSTIVE_VALID_DATASET) {
-                    /*
-                     * First check if one of the identifiers can be used to 
find directly an identified object.
-                     * Verify that the object that we found is actually equal 
to given one; we do not blindly
-                     * trust the identifiers in the user object.
-                     */
-                    IdentifiedObject candidate = createFromIdentifiers(object);
-                    if (candidate != null) {
-                        result = Set.of(candidate);
-                    }
-                }
-                /*
-                 * Here we exhausted the quick paths.
-                 * Perform a full scan (costly) if we are allowed to, 
otherwise abandon.
-                 */
-                if (result == null) {
-                    if (domain == Domain.DECLARATION) {
-                        result = Set.of();
-                    } else {
-                        result = createFromCodes(object);
-                    }
-                }
-            } finally {
-                proxy = previousProxy;
-            }
-            result = cache(object, result);     // Costly operations (even if 
the result is empty) are worth to cache.
-        }
-        return result;
-    }
-
     /**
      * Lookups only one object which is approximately equal to the specified 
object.
      * This method invokes {@link #find(IdentifiedObject)}, then examine the 
returned {@code Set} as below:
@@ -364,7 +339,7 @@ public class IdentifiedObjectFinder {
      *
      * @param  object  the object looked up.
      * @return the identified object, or {@code null} if none or ambiguous.
-     * @throws FactoryException if an error occurred while creating an object.
+     * @throws FactoryException if an error occurred while fetching the 
authority code candidates.
      */
     public IdentifiedObject findSingleton(final IdentifiedObject object) 
throws FactoryException {
         /*
@@ -376,15 +351,15 @@ public class IdentifiedObjectFinder {
         boolean ambiguous = false;
         try {
             for (final IdentifiedObject candidate : find(object)) {
-                final boolean equalsIncludingAxes = !ignoreAxes || 
Utilities.deepEquals(candidate, object, COMPARISON_MODE);
+                boolean matchAxes = !ignoreAxes || 
Utilities.deepEquals(candidate, object, ComparisonMode.APPROXIMATE);
                 if (result != null) {
                     ambiguous = true;
-                    if (sameAxisOrder & equalsIncludingAxes) {
+                    if (sameAxisOrder & matchAxes) {
                         return null;            // Found two matches even when 
taking in account axis order.
                     }
                 }
                 result = candidate;
-                sameAxisOrder = equalsIncludingAxes;
+                sameAxisOrder = matchAxes;
             }
         } catch (BackingStoreException e) {
             throw e.unwrapOrRethrow(FactoryException.class);
@@ -393,110 +368,177 @@ public class IdentifiedObjectFinder {
     }
 
     /**
-     * Creates an object equals (optionally ignoring metadata), to the 
specified object
-     * using only the {@linkplain AbstractIdentifiedObject#getIdentifiers 
identifiers}.
-     * If no such object is found, returns {@code null}.
+     * Lookups objects which are approximately equal to the specified object.
+     * This method tries to instantiate objects identified by the {@linkplain 
#getCodeCandidates set of candidate codes}
+     * using the {@linkplain #factory authority factory} specified at 
construction time.
+     * {@link FactoryException}s thrown during object creations are logged and 
otherwise ignored.
+     * The successfully created objects which are equal to the specified 
object in the sense of
+     * {@link ComparisonMode#APPROXIMATE} or {@link 
ComparisonMode#ALLOW_VARIANT ALLOW_VARIANT}
+     * (depending on whether {@linkplain #isIgnoringAxes() axes are ignored}) 
are included in the returned set.
      *
-     * <p>This method may be used in order to get a fully identified object 
from a partially identified one.</p>
+     * <h4>Exception handling</h4>
+     * This method may return a lazy set, in which case some checked 
exceptions may occur at iteration time.
+     * These exceptions are wrapped in a {@link BackingStoreException}.
      *
      * @param  object  the object looked up.
-     * @return the identified object, or {@code null} if not found.
-     * @throws FactoryException if an error occurred while creating an object.
-     *
-     * @see #createFromCodes(IdentifiedObject)
+     * @return the identified objects, or an empty set if not found.
+     * @throws FactoryException if an error occurred while fetching the 
authority code candidates.
      */
-    private IdentifiedObject createFromIdentifiers(final IdentifiedObject 
object) throws FactoryException {
-        for (final Identifier id : object.getIdentifiers()) {
-            final String code = IdentifiedObjects.toString(id);
-            /*
-             * We will process only codes with a namespace (e.g. 
"AUTHORITY:CODE") for avoiding ambiguity.
-             * We do not try to check by ourselves if the identifier is in the 
namespace of the factory,
-             * because calling factory.getAuthorityCodes() or 
factory.getCodeSpaces() may be costly for
-             * some implementations.
-             */
-            if (code.indexOf(Constants.DEFAULT_SEPARATOR) >= 0) {
-                final IdentifiedObject candidate;
-                try {
-                    candidate = create(code);
-                } catch (NoSuchAuthorityCodeException e) {
-                    // The identifier was not recognized. No problem, let's go 
on.
-                    exceptionOccurred(e);
-                    continue;
-                }
-                if (match(candidate, object)) {
-                    return candidate;
-                }
+    public Set<IdentifiedObject> find(final IdentifiedObject object) throws 
FactoryException {
+        Set<IdentifiedObject> result = 
getFromCache(Objects.requireNonNull(object));
+        if (result == null) try {
+            final AuthorityFactoryProxy<?> previousProxy = proxy;
+            proxy = AuthorityFactoryProxy.getInstance(object.getClass());
+            try {
+                result = createFromCodes(object);
+            } finally {
+                proxy = previousProxy;
             }
+            result = cache(object, result);     // Costly operations (even if 
the result is empty) are worth to cache.
+        } catch (BackingStoreException e) {
+            throw e.unwrapOrRethrow(FactoryException.class);
         }
-        return null;
+        return result;
     }
 
     /**
-     * Creates an object equals (optionally ignoring metadata), to the 
specified object.
-     * This method scans the {@linkplain #getCodeCandidates(IdentifiedObject) 
authority codes},
-     * creates the objects and returns the first one which is equal to the 
specified object in
-     * the sense of {@link Utilities#deepEquals(Object, Object, 
ComparisonMode)}.
+     * Creates objects approximately equal to the specified object by 
iterating over authority code candidates.
+     * This method is invoked by {@link #find(IdentifiedObject)} after it has 
been verified that the result is not in the cache.
+     * The default implementation iterates over the {@linkplain 
#getCodeCandidates(IdentifiedObject) authority code candidates},
+     * creates the objects and returns the ones which are approximately equal 
to the specified object.
      *
-     * <p>This method may be used in order to get a fully {@linkplain 
AbstractIdentifiedObject identified object}
-     * from an object without {@linkplain 
AbstractIdentifiedObject#getIdentifiers() identifiers}.</p>
+     * <h4>Exception handling</h4>
+     * This method may return a lazy set, in which case some checked 
exceptions may occur at iteration time.
+     * These exceptions are wrapped in a {@link BackingStoreException}.
      *
      * @param  object  the object looked up.
-     * @return the identified object, or {@code null} if not found.
-     * @throws FactoryException if an error occurred while scanning through 
authority codes.
-     *
-     * @see #createFromIdentifiers(IdentifiedObject)
+     * @return the identified objects, or an empty set if not found.
+     * @throws FactoryException if an error occurred while fetching the 
authority code candidates.
+     * @throws BackingStoreException allowed for convenience, will be 
unwrapped by the caller.
      */
     Set<IdentifiedObject> createFromCodes(final IdentifiedObject object) 
throws FactoryException {
-        final var result = new LinkedHashSet<IdentifiedObject>();     // We 
need to preserve order.
-        final boolean finer = 
Semaphores.queryAndSet(Semaphores.FINER_OBJECT_CREATION_LOGS);
-        try {
-            for (final String code : getCodeCandidates(object)) {
-                final IdentifiedObject candidate;
-                try {
-                    candidate = create(code);
-                } catch (FactoryException e) {
-                    exceptionOccurred(e);
-                    continue;
-                }
-                if (match(candidate, object)) {
-                    result.add(candidate);
+        return new Instances(this, object);
+    }
+
+    /**
+     * The set of geodetic instances created from the code candidates.
+     * This is a lazy set, where each instance is created only when first 
requested.
+     * Checked exceptions are wrapped in {@link BackingStoreException}.
+     *
+     * <h2>Implementation note</h2>
+     * This class should not keep a reference to the enclosing class (it is 
static for that reason),
+     * because some subclasses of {@link IdentifiedObjectFinder} are 
short-lived data access objects
+     * holding resources such as database connections.
+     */
+    private static final class Instances extends LazySet<IdentifiedObject> 
implements Function<String, IdentifiedObject> {
+        /** Copy of {@link IdentifiedObjectFinder#factory}. */
+        private final AuthorityFactory factory;
+
+        /** Snapshot of {@link IdentifiedObjectFinder#proxy}. */
+        private final AuthorityFactoryProxy<?> proxy;
+
+        /** The authority codes form which to create object candidates. */
+        private final Iterable<String> codes;
+
+        /** The comparison mode for deciding if a candidate is a match. */
+        private final ComparisonMode mode;
+
+        /** Previously created objects for removing duplicates. */
+        private final Set<IdentifiedObject> existing;
+
+        /** The user-specified object to search. */
+        private final IdentifiedObject object;
+
+        /** Whether at least one match has been found. */
+        private boolean hasMatches;
+
+        /**
+         * Creates a new collection for the given object to search.
+         *
+         * @param  factory  value of {@link IdentifiedObjectFinder#factory}.
+         * @param  object   the user-specified object to search.
+         * @throws FactoryException if an error occurred while fetching the 
authority code candidates.
+         */
+        Instances(final IdentifiedObjectFinder source, final IdentifiedObject 
object) throws FactoryException {
+            factory  = source.factory;
+            proxy    = source.proxy;
+            codes    = source.getCodeCandidates(object);
+            mode     = source.getComparisonMode();
+            existing = new HashSet<>();
+            this.object = object;
+        }
+
+        /**
+         * Creates the iterator which will create objects from authority codes.
+         * This method will be invoked only when first needed and at most 
once, unless {@link #reload()} is invoked.
+         *
+         * @return iterator over objects created from authority codes.
+         */
+        @Override
+        protected Iterator<IdentifiedObject> createSourceIterator() {
+            return new FilteredIterator<>(codes.iterator(), this);
+        }
+
+        /**
+         * Creates an object from the given code, and verifies if it is 
considered equals to the object to search.
+         * This method is invoked by {@link FilteredIterator}. Checked 
exceptions are logged. If the object cannot
+         * be created or is not approximately equal to the object to search, 
then this method returns {@code true},
+         * which is understood by {@link FilteredIterator} as an instruction 
to look for the next element.
+         *
+         * @param  code  one of the authority codes returned by {@link 
#getCodeCandidates(IdentifiedObject)}.
+         * @return object created from the given code if it is approximately 
equal to the object to search.
+         */
+        @Override
+        public IdentifiedObject apply(final String code) {
+            final boolean finer = 
Semaphores.queryAndSet(Semaphores.FINER_OBJECT_CREATION_LOGS);
+            try {
+                IdentifiedObject candidate = (IdentifiedObject) 
proxy.createFromAPI(factory, code);
+                if (match(candidate, object, mode, proxy) && 
existing.add(candidate)) {
+                    if (!hasMatches) {
+                        hasMatches = true;
+                        if (codes instanceof Disposable) {
+                            ((Disposable) codes).dispose();   // For stopping 
iteration after the easy matches.
+                        }
+                    }
+                    return candidate;
                 }
+            } catch (FactoryException e) {
+                exceptionOccurred(e);
+            } finally {
+                Semaphores.clearIfFalse(Semaphores.FINER_OBJECT_CREATION_LOGS, 
finer);
             }
-        } catch (BackingStoreException e) {
-            throw e.unwrapOrRethrow(FactoryException.class);
-        } finally {
-            Semaphores.clearIfFalse(Semaphores.FINER_OBJECT_CREATION_LOGS, 
finer);
+            return null;
         }
-        return result;
     }
 
     /**
-     * Creates an object for the given identifier, name or alias. This method 
is invoked by the default
-     * {@link #find(IdentifiedObject)} method implementation with the 
following argument values, in order
-     * (from less expensive to most expensive search operation):
-     *
-     * <ol>
-     *   <li>All {@linkplain AbstractIdentifiedObject#getIdentifier() 
identifiers} of the object to search,
-     *       formatted in an {@linkplain 
IdentifiedObjects#toString(Identifier) "AUTHORITY:CODE"} pattern.</li>
-     *   <li>The {@linkplain AbstractIdentifiedObject#getName() name} of the 
object to search,
-     *       {@linkplain org.apache.sis.referencing.NamedIdentifier#getCode() 
without authority}.</li>
-     *   <li>All {@linkplain AbstractIdentifiedObject#getAlias() aliases} of 
the object to search.</li>
-     *   <li>Each code returned by the {@link 
#getCodeCandidates(IdentifiedObject)} method, in iteration order.</li>
-     * </ol>
-     *
-     * @param  code  the authority code for which to create an object.
-     * @return the identified object for the given code, or {@code null} to 
stop attempts.
+     * Creates an object from the given code, and verifies if it is considered 
equals to the object to search.
+     * This is a helper method for subclasses. This method does the same work 
as {@link Instances}, but allows
+     * the subclass to specify an alternative authority factory.
+     *
+     * @param  factory  the authority factory to use. This is not necessarily 
{@link #factory}.
+     * @param  code     the authority code for which to create an object 
candidate.
+     * @param  object   the user-specified object to search.
+     * @return instance for the given code, or {@code null} if not 
approximately equal to the object to search.
      * @throws NoSuchAuthorityCodeException if no object is found for the 
given code. It may happen if {@code code}
      *         was a name or alias instead of an identifier and the factory 
supports only search by identifier.
      * @throws FactoryException if an error occurred while creating the object.
      */
-    private IdentifiedObject create(final String code) throws FactoryException 
{
-        return (IdentifiedObject) proxy.createFromAPI(factory, code);
+    final IdentifiedObject createAndFilter(final AuthorityFactory factory, 
final String code, final IdentifiedObject object)
+            throws FactoryException
+    {
+        final boolean finer = 
Semaphores.queryAndSet(Semaphores.FINER_OBJECT_CREATION_LOGS);
+        try {
+            final var candidate = (IdentifiedObject) 
proxy.createFromAPI(factory, code);
+            return match(candidate, object, getComparisonMode(), proxy) ? 
candidate : null;
+        } finally {
+            Semaphores.clearIfFalse(Semaphores.FINER_OBJECT_CREATION_LOGS, 
finer);
+        }
     }
 
     /**
      * Returns a set of authority codes that <em>may</em> identify the same 
object as the specified one.
-     * The elements may be determined from object identifiers, from object 
names, or from a more extensive search in the database.
+     * The codes may be determined from object identifiers, names, aliases or 
extensive search in the geodetic dataset.
      * The effort in populating the returned set is specified by the 
{@linkplain #getSearchDomain() search domain}.
      * The returned set should contain at least the codes of every objects in 
the search domain
      * that are {@linkplain ComparisonMode#APPROXIMATE approximately equal} to 
the specified object.
@@ -513,23 +555,167 @@ public class IdentifiedObjectFinder {
      * The exception cause is often the checked {@link FactoryException}.
      *
      * <h4>Default implementation</h4>
-     * The default implementation returns the same set as
-     * <code>{@linkplain GeodeticAuthorityFactory#getAuthorityCodes(Class) 
getAuthorityCodes}(type)</code>
-     * where {@code type} is the interface specified at construction type.
+     * The default implementation returns codes defined from {@code 
object.getIdentifiers()},
+     * or {@code factory.getAuthorityCodes(type)} where {@code type} is 
derived from {@code object} class,
+     * or a combination of both collection, depending on the {@linkplain 
#getSearchDomain() search domain}.
      * Subclasses should override this method in order to return a smaller 
set, if they can.
      *
      * @param  object  the object looked up.
      * @return a set of code candidates.
      * @throws FactoryException if an error occurred while fetching the set of 
code candidates.
+     *
+     * @see IdentifiedObject#getIdentifiers()
+     * @see AuthorityFactory#getAuthorityCodes(Class)
      */
     protected Iterable<String> getCodeCandidates(final IdentifiedObject 
object) throws FactoryException {
-        return 
factory.getAuthorityCodes(proxy.type.asSubclass(IdentifiedObject.class));
+        /*
+         * Undocumented contract: if the Iterable implements `Dispose`, its 
method will be invoked
+         * after the first match has been found. This is interpreted as a hint 
that the iteration
+         * can stop earlier than it would normally do.
+         */
+        final Class<? extends IdentifiedObject> type = 
proxy.type.asSubclass(IdentifiedObject.class);
+        if (domain == Domain.EXHAUSTIVE_VALID_DATASET) {
+            return factory.getAuthorityCodes(type);
+        }
+        final boolean easy = (domain == Domain.DECLARATION);
+        final Set<Identifier> identifiers = object.getIdentifiers();
+        if (identifiers.isEmpty()) {
+            return easy ? Set.of() : factory.getAuthorityCodes(type);
+        }
+        return new Codes(factory, identifiers, easy ? null : type);
+    }
+
+    /**
+     * Union of identifier codes followed by code candidates fetched from the 
geodetic dataset.
+     * The codes returned by this iterable are, in this order:
+     *
+     * <ol>
+     *   <li>{@link IdentifiedObject#getIdentifiers()} (filtered with {@link 
#apply(Identifier)})</li>
+     *   <li>{@link AuthorityFactory#getAuthorityCodes(Class)} (skipped if the 
class is null)</li>
+     * </ol>
+     *
+     * <h2>Implementation note</h2>
+     * This class should not keep a reference to the enclosing class (it is 
static for that reason),
+     * because some subclasses of {@link IdentifiedObjectFinder} are 
short-lived data access objects
+     * holding resources such as database connections.
+     */
+    private static final class Codes implements Iterable<String>, 
Function<Identifier, String>, Disposable {
+        /** Copy of {@link IdentifiedObjectFinder#factory}. */
+        private final AuthorityFactory factory;
+
+        /** The identifiers of the object to search. */
+        private final Set<Identifier> identifiers;
+
+        /** Type of objects to request for code candidates, or {@code null} 
for not requesting code candidates. */
+        private Class<? extends IdentifiedObject> type;
+
+        /** Code candidates, created when first needed. This collection may be 
costly to create and/or to use. */
+        private Iterable<String> codes;
+
+        /**
+         * Creates a new union of identifier codes and candidate codes.
+         *
+         * @param factory      value of {@link IdentifiedObjectFinder#factory}.
+         * @param identifiers  identifiers of the object to search.
+         * @param type         type of objects to request for code candidates, 
or {@code null}.
+         */
+        Codes(final AuthorityFactory factory, final Set<Identifier> 
identifiers, final Class<? extends IdentifiedObject> type) {
+            this.factory     = factory;
+            this.identifiers = identifiers;
+            this.type        = type;
+        }
+
+        /**
+         * Invoked when the caller requested to stop the iteration after the 
current group of elements.
+         * A group of elements is either the codes specified by the 
identifiers, or the codes found in
+         * the database. We will avoid to stop in the middle of a group.
+         *
+         * <p>This is an undocumented feature of {@link 
#createFromCodes(IdentifiedObject)}
+         * for stopping an iteration early when at least one match has been 
found.</p>
+         */
+        @Override
+        public void dispose() {
+            type = null;
+        }
+
+        /**
+         * Converts the given identifier to a code returned by {@link 
#getIdentifiers()}.
+         * We accept only codes with a namespace (e.g. "AUTHORITY:CODE") for 
avoiding ambiguity.
+         * We do not try to check by ourselves if the identifier is in the 
namespace of the factory,
+         * because calling {@code factory.getAuthorityCodes()} or {@code 
factory.getCodeSpaces()}
+         * may be costly for some implementations.
+         */
+        @Override
+        public String apply(final Identifier id) {
+            final String code = IdentifiedObjects.toString(id);
+            return (code.indexOf(Constants.DEFAULT_SEPARATOR) >= 0) ? code : 
null;
+        }
+
+        /**
+         * Returns an iterator over codes of the identifiers of the object to 
search.
+         * The iteration does not include the {@code Identifier} of the name 
because, at least
+         * in Apache <abbr>SIS</abbr> implementations, the factories that 
accept object names
+         * already override {@link #getCodeCandidates(IdentifiedObject)} for 
including names.
+         */
+        final Iterator<String> getIdentifiers() {
+            return new FilteredIterator<>(identifiers.iterator(), this);
+        }
+
+        /**
+         * Returns an iterator over the code candidates. This method should be 
invoked only in last resort.
+         *
+         * @throws BackingStoreException if an error occurred while fetching 
the collection of authority codes.
+         */
+        final Iterator<String> getAuthorityCodes() {
+            if (codes == null) {
+                if (type == null) {
+                    codes = Set.of();
+                } else try {
+                    codes = factory.getAuthorityCodes(type);
+                } catch (FactoryException e) {
+                    throw new BackingStoreException(e);
+                }
+            }
+            return codes.iterator();
+        }
+
+        /**
+         * Returns an iterator over the identifiers followed by the code 
candidates.
+         */
+        @Override
+        public Iterator<String> iterator() {
+            return new Iterator<String>() {
+                /** The iterator of the code to return. */
+                private Iterator<String> codes = getIdentifiers();
+
+                /** Whether {@link #codes} is for code candidates. */
+                private boolean isCodeCandidates;
+
+                /** Returns whether there is more codes to return. */
+                @Override public boolean hasNext() {
+                    if (!isCodeCandidates) {
+                        if (codes.hasNext()) return true;
+                        codes = getAuthorityCodes();
+                        isCodeCandidates = true;
+                    }
+                    return codes.hasNext();
+                }
+
+                /** Returns the next code. */
+                @Override public String next() {
+                    if (!(isCodeCandidates || codes.hasNext())) {
+                        codes = getAuthorityCodes();
+                    }
+                    return codes.next();
+                }
+            };
+        }
     }
 
     /**
      * Invoked when an exception occurred during the creation of a candidate 
from a code.
      */
-    private static void exceptionOccurred(final FactoryException exception) {
+    static void exceptionOccurred(final FactoryException exception) {
         if (GeodeticAuthorityFactory.LOGGER.isLoggable(Level.FINER)) {
             /*
              * use `getMessage()` instead of `getLocalizedMessage()` for
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 9e819db570..1c049568bb 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
@@ -42,6 +42,7 @@ import org.opengis.referencing.cs.*;
 import org.opengis.referencing.crs.*;
 import org.opengis.referencing.datum.*;
 import org.opengis.referencing.operation.*;
+import org.opengis.metadata.Identifier;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.metadata.extent.Extent;
 import org.opengis.parameter.ParameterDescriptor;
@@ -857,7 +858,7 @@ public class MultiAuthoritiesFactory extends 
GeodeticAuthorityFactory implements
          * rigorous approach in a future SIS version.
          */
         if (parameters != null || code.indexOf(CommonAuthorityCode.SEPARATOR) 
>= 0) {
-            final StringBuilder buffer = new StringBuilder(authority.length() 
+ code.length() + 1)
+            final var buffer = new StringBuilder(authority.length() + 
code.length() + 1)
                     
.append(authority).append(Constants.DEFAULT_SEPARATOR).append(code);
             if (parameters != null) {
                 for (final String p : parameters) {
@@ -1738,12 +1739,12 @@ public class MultiAuthoritiesFactory extends 
GeodeticAuthorityFactory implements
      * A {@link IdentifiedObjectFinder} which tests every factories declared 
in the
      * {@linkplain MultiAuthoritiesFactory#getAllFactories() collection of 
factories}.
      */
-    private static class Finder extends IdentifiedObjectFinder {
+    private static final class Finder extends IdentifiedObjectFinder {
         /**
          * The finders of all factories, or {@code null} if not yet fetched.
          * We will create this array only when first needed in order to avoid 
instantiating the factories
-         * before needed (for example we may be able to find an object using 
only its code). However if we
-         * need to create this array, then we will create it fully (for all 
factories at once).
+         * before needed (for example we may be able to find an object using 
only its code). However,
+         * if we need to create this array, then we will create it fully (for 
all factories at once).
          */
         private IdentifiedObjectFinder[] finders;
 
@@ -1781,18 +1782,77 @@ public class MultiAuthoritiesFactory extends 
GeodeticAuthorityFactory implements
         }
 
         /**
-         * Delegates to every factories registered in the enclosing {@link 
MultiAuthoritiesFactory},
-         * in iteration order. This method is invoked only if the parent class 
failed to find the
-         * object by its identifiers and by its name. At this point, as a last 
resort, we will scan
-         * over the objects in the database.
+         * Creates objects equal (optionally ignoring metadata) to the 
specified object using the given identifiers.
+         * This method may be used in order to get a fully identified object 
from a partially identified one.
+         *
+         * @param  type         the type of the factory which is needed.
+         * @param  object       the user-specified object to search.
+         * @param  identifiers  {@code object.getIdentifiers()} or {@code 
object.getName()}.
+         * @param  result       the collection where to add the object 
instantiated from the identifiers.
+         * @throws FactoryException if an error occurred while creating an 
object.
+         */
+        private void createFromIdentifiers(final 
AuthorityFactoryIdentifier.Type type, final IdentifiedObject object,
+                final Iterable<Identifier> identifiers, final 
Set<IdentifiedObject> result) throws FactoryException
+        {
+            for (final Identifier identifier : identifiers) {
+                final String authority = identifier.getCodeSpace();
+                if (authority != null) {
+                    @SuppressWarnings("LocalVariableHidesMemberVariable")
+                    final var factory = (MultiAuthoritiesFactory) this.factory;
+                    final IdentifiedObject candidate;
+                    try {
+                        final var fid = 
AuthorityFactoryIdentifier.create(type, authority, identifier.getVersion());
+                        candidate = 
createAndFilter(factory.getAuthorityFactory(fid), identifier.getCode(), object);
+                    } catch (NoSuchAuthorityCodeException e) {
+                        // The identifier was not recognized. No problem, 
let's go on.
+                        exceptionOccurred(e);
+                        continue;
+                    }
+                    if (candidate != null) {
+                        result.add(candidate);
+                    }
+                }
+            }
+        }
+
+        /**
+         * Creates objects approximately equal to the specified object by 
iterating over authority code candidates.
+         * This method is invoked by {@link #find(IdentifiedObject)} when the 
result was not already in the cache.
+         * First, this method tries to delegate to the factory specified by 
the name space of each identifier.
+         * If this quick search using declared identifiers did not worked, 
then this method delegates to every
+         * factories registered in the enclosing {@link 
MultiAuthoritiesFactory}, in iteration order.
          *
          * <p>This method shall <strong>not</strong> delegate the job to the 
parent class, as the default
          * implementation in the parent class is very inefficient. We need to 
delegate to the finders of
          * all factories, so we can leverage their potentially more efficient 
algorithms.</p>
+         *
+         * @param  object  the object looked up.
+         * @return the identified objects, or an empty set if not found.
+         * @throws FactoryException if an error occurred while fetching the 
authority code candidates.
+         * @throws BackingStoreException allowed for convenience, will be 
unwrapped by the caller.
          */
         @Override
         final Set<IdentifiedObject> createFromCodes(final IdentifiedObject 
object) throws FactoryException {
-            if (finders == null) try {
+            if (getSearchDomain() != Domain.EXHAUSTIVE_VALID_DATASET) {
+                for (AuthorityFactoryIdentifier.Type type : 
AuthorityFactoryIdentifier.Type.values()) {
+                    if (type.api.isInstance(object)) {
+                        final var result = new 
LinkedHashSet<IdentifiedObject>();
+                        createFromIdentifiers(type, object, 
object.getIdentifiers(), result);
+                        if (result.isEmpty()) {
+                            createFromIdentifiers(type, object, 
CollectionsExt.singletonOrEmpty(object.getName()), result);
+                            if (result.isEmpty()) {
+                                break;
+                            }
+                        }
+                        return result;
+                    }
+                }
+            }
+            /*
+             * No object created from the identifiers or the name.
+             * Prepare finders for each factory in iteration order.
+             */
+            if (finders == null) {
                 final var list = new ArrayList<IdentifiedObjectFinder>();
                 final var unique = new 
IdentityHashMap<AuthorityFactory,Boolean>();
                 final Iterator<AuthorityFactory> it = 
((MultiAuthoritiesFactory) factory).getAllFactories();
@@ -1801,21 +1861,35 @@ public class MultiAuthoritiesFactory extends 
GeodeticAuthorityFactory implements
                     if (candidate instanceof GeodeticAuthorityFactory && 
unique.put(candidate, Boolean.TRUE) == null) {
                         IdentifiedObjectFinder finder = 
((GeodeticAuthorityFactory) candidate).newIdentifiedObjectFinder();
                         if (finder != null) {   // Should never be null 
according method contract, but we are paranoiac.
-                            
finder.setSearchDomain(Domain.EXHAUSTIVE_VALID_DATASET);
-                            finder.setWrapper(this);
                             list.add(finder);
                         }
                     }
                 }
                 finders = list.toArray(IdentifiedObjectFinder[]::new);
-            } catch (BackingStoreException e) {
-                throw e.unwrapOrRethrow(FactoryException.class);
             }
-            final var found = new LinkedHashSet<IdentifiedObject>();
+            /*
+             * If only one finder returns a non-empty set, we return that set 
without copying
+             * the elements in a `LinkedHashSet` because it may a lazy set. We 
merge the sets
+             * only if really necessary.
+             */
+            Set<IdentifiedObject> merged = null, result = null;
             for (final IdentifiedObjectFinder finder : finders) {
-                found.addAll(finder.find(object));
+                finder.setWrapper(this);    // Also copy the configuration of 
this finder.
+                final Set<IdentifiedObject> codes = finder.find(object);
+                if (!codes.isEmpty()) {
+                    if (result == null) {
+                        result = codes;
+                    } else {
+                        if (merged == null) {
+                            merged = new LinkedHashSet<>(result);
+                        }
+                        if (merged.addAll(codes)) {
+                            result = merged;
+                        }
+                    }
+                }
             }
-            return found;
+            return (result != null) ? result : Set.of();
         }
     }
 
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 2b51586929..d7a3d64524 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
@@ -44,6 +44,7 @@ import org.opengis.referencing.datum.TemporalDatum;
 import org.opengis.referencing.datum.VerticalDatum;
 import org.opengis.referencing.datum.EngineeringDatum;
 import org.apache.sis.util.ArraysExt;
+import org.apache.sis.util.Disposable;
 import org.apache.sis.util.Exceptions;
 import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.logging.Logging;
@@ -75,6 +76,10 @@ import org.opengis.referencing.crs.DerivedCRS;
  * An implementation of {@link IdentifiedObjectFinder} which scans over a 
smaller set of authority codes.
  * This is used for finding the EPSG code of a given Coordinate Reference 
System or other geodetic object.
  *
+ * <h4>Lifetime</h4>
+ * The finder returned by this method depends on the {@link EPSGDataAccess} 
instance given to the constructor.
+ * The finder should not be used after this factory has been closed or given 
back to the {@link EPSGFactory}.
+ *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  */
 final class EPSGCodeFinder extends IdentifiedObjectFinder {
@@ -152,7 +157,7 @@ final class EPSGCodeFinder extends IdentifiedObjectFinder {
     private <T extends IdentifiedObject> Condition dependencies(final String 
column,
             final Class<T> type, final T dependency, final boolean ignoreAxes) 
throws FactoryException
     {
-        if (dependency != null) {
+        if (dependency != null) try {
             final Class<? extends IdentifiedObject> pt = declaredType;
             final boolean previous = isIgnoringAxes();
             final Set<IdentifiedObject> find;
@@ -176,6 +181,8 @@ final class EPSGCodeFinder extends IdentifiedObjectFinder {
             if (!filters.isEmpty()) {
                 return new Condition(column, filters);
             }
+        } catch (BackingStoreException e) {
+            throw e.unwrapOrRethrow(FactoryException.class);
         }
         return null;
     }
@@ -594,7 +601,7 @@ crs:    if (isInstance(CoordinateReferenceSystem.class, 
object)) {
     @Override
     protected Iterable<String> getCodeCandidates(final IdentifiedObject 
object) throws FactoryException {
         for (final TableInfo source : TableInfo.values()) {
-            if (source.isCandidate() && source.type.isInstance(object)) try {
+            if (source.isSpecificEnough() && source.type.isInstance(object)) 
try {
                 return new CodeCandidates(object, source);
             } catch (SQLException exception) {
                 throw databaseFailure(exception);
@@ -606,8 +613,13 @@ crs:    if (isInstance(CoordinateReferenceSystem.class, 
object)) {
     /**
      * Set of authority codes that <strong>may</strong> identify the same 
object as the specified one.
      * This collection returns the codes that can be obtained easily before 
the more expensive searches.
+     *
+     * @todo We should not keep a reference to the enclosing finder, because 
the {@link EPSGDataAccess}
+     * may become invalid before the iteration is completed. For now, this is 
not a problem because this
+     * collection is copied by the {@link EPSGFactory} finder. But this is 
suboptimal because it defeats
+     * the purpose of object lazy instantiation.
      */
-    private final class CodeCandidates implements Iterable<String> {
+    private final class CodeCandidates implements Iterable<String>, Disposable 
{
         /** The object to search. */
         private final IdentifiedObject object;
 
@@ -661,6 +673,19 @@ crs:    if (isInstance(CoordinateReferenceSystem.class, 
object)) {
             }
         }
 
+        /**
+         * Invoked when the caller requested to stop the iteration after the 
current group of elements.
+         * A group of elements is either the codes specified by the 
identifiers, or the codes found in
+         * the database. We will avoid to stop in the middle of a group.
+         *
+         * <p>This is an undocumented feature of {@link 
#createFromCodes(IdentifiedObject)}
+         * for stopping an iteration early when at least one match has been 
found.</p>
+         */
+        @Override
+        public void dispose() {
+            searchMethod = 3;   // The value after the last switch in 
`fetchMoreCodes(Collection)`.
+        }
+
         /**
          * Populates the given collection with code candidates.
          * This method tries less expansive search methods before to tries 
more expensive search methods.
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 d39afd31b2..f3e64af99e 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
@@ -601,7 +601,7 @@ public class EPSGDataAccess extends 
GeodeticAuthorityFactory implements CRSAutho
         }
         if (source == null) {
             for (TableInfo c : TableInfo.values()) {
-                if (c.isCandidate() && c.type.isAssignableFrom(type)) {
+                if (c.isSpecificEnough() && c.type.isAssignableFrom(type)) {
                     if (source != null) {
                         return null;        // The specified type is too 
generic.
                     }
@@ -1436,7 +1436,7 @@ search: try (ResultSet result = 
executeMetadataQuery("Deprecation",
         try {
             final int key = isPrimaryKey ? toPrimaryKeys(null, code)[0] : 0;
             for (final TableInfo source : TableInfo.values()) {
-                if (!(source.isCandidate() && 
IdentifiedObject.class.isAssignableFrom(source.type))) {
+                if (!(source.isSpecificEnough() && 
IdentifiedObject.class.isAssignableFrom(source.type))) {
                     continue;
                 }
                 final String column = isPrimaryKey ? source.codeColumn : 
source.nameColumn;
@@ -3432,6 +3432,11 @@ next:                   while (r.next()) {
      * The finder tries to fetch a fully {@linkplain AbstractIdentifiedObject 
identified object} from an incomplete one,
      * for example from an object without "{@code ID[…]}" or "{@code 
AUTHORITY[…]}" element in <i>Well Known Text</i>.
      *
+     * <h4>Lifetime</h4>
+     * The finder returned by this method depends on this {@code 
EPSGDataAccess} instance.
+     * The finder should not be used after this factory has been {@linkplain 
#close() closed}
+     * or given back to the {@link EPSGFactory}.
+     *
      * @return a finder to use for looking up unidentified objects.
      * @throws FactoryException if the finder cannot be created.
      */
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 aed9fa2b7c..b6d6726fa9 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
@@ -92,7 +92,7 @@ enum TableInfo {
      * Information about the "Conventional RS" table.
      * This enumeration usually needs to be ignored because the current type 
is too generic.
      *
-     * @see #isCandidate()
+     * @see #isSpecificEnough()
      */
     CONVENTIONAL_RS(IdentifiedObject.class,
             "\"Conventional RS\"",
@@ -281,7 +281,7 @@ enum TableInfo {
      * Returns whether this enumeration value can be used when looking a table 
by an object type.
      * This method returns {@code false} for types that are too generic.
      */
-    final boolean isCandidate() {
+    final boolean isSpecificEnough() {
         return type != IdentifiedObject.class;
     }
 
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/EPSGParameterDomain.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/EPSGParameterDomain.java
index cb257a5f5e..9fd0fe7db5 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/EPSGParameterDomain.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/EPSGParameterDomain.java
@@ -23,9 +23,9 @@ import org.apache.sis.util.privy.CollectionsExt;
 
 
 /**
- * The domain of values of an EPSG parameter which accepts different units.
+ * The domain of values of an <abbr>EPSG</abbr> parameter which accepts 
different units.
  * An example is the EPSG:8617 (<cite>Coordinate 1 of evaluation point</cite>) 
parameter,
- * which may be used in the EPSG database with either metres or degrees units.
+ * which may be used in the <abbr>EPSG</abbr> database with either metres or 
degrees units.
  *
  * @author  Martin Desruisseaux (Geomatys)
  */
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/MergedProperties.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/MergedProperties.java
index 907cdf0e43..978203d41f 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/MergedProperties.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/MergedProperties.java
@@ -23,8 +23,8 @@ import org.apache.sis.util.privy.AbstractMap;
 
 /**
  * A map which first looks for values in a user supplied map, then looks in a 
default map if no value was found
- * in the user supplied map. This map is for {@link 
org.apache.sis.referencing.factory.GeodeticObjectFactory} and
- * other SIS factories internal usage only.
+ * in the user supplied map. This map is for {@link 
org.apache.sis.referencing.factory.GeodeticObjectFactory}
+ * and other <abbr>SIS</abbr> factories internal usage only.
  *
  * @author  Martin Desruisseaux (Geomatys)
  */
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 786face47f..3c45c52ffc 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
@@ -344,6 +344,8 @@ class CoordinateOperationRegistry {
             } catch (UnavailableFactoryException e) {
                 log(null, e);
                 codeFinder = null;
+            } catch (BackingStoreException e) {
+                throw e.unwrapOrRethrow(FactoryException.class);
             }
             authorityCodes.put(crs, codes);
         }
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/FilteredIterator.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/FilteredIterator.java
new file mode 100644
index 0000000000..1d894f3410
--- /dev/null
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/FilteredIterator.java
@@ -0,0 +1,113 @@
+/*
+ * 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.privy;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+
+/**
+ * An iterator based on another iterator with some elements excluded.
+ * The exclusion is implemented by looking ahead what is next element.
+ * This iterator can also convert the element type.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ *
+ * @param <S> the type of elements returned by the source iterator.
+ * @param <T> the type of elements returned by this iterator.
+ */
+public final class FilteredIterator<S,T> implements Iterator<T> {
+    /**
+     * The source iterator.
+     */
+    private final Iterator<S> source;
+
+    /**
+     * Converter from elements of the source iterator to elements of this 
iterator.
+     * If the mapper returns {@code null}, then that element will be skipped.
+     */
+    private final Function<S,T> mapper;
+
+    /**
+     * The next element to return, or {@code null} if unknown.
+     */
+    private T next;
+
+    /**
+     * Creates a new filtered iterator.
+     * Elements will be converted using the given mapper.
+     * If the mapper returns {@code null} for a given element, then that 
element will be skipped.
+     *
+     * @param  source  the source iterator.
+     * @param  mapper  converter from elements of the source iterator to 
elements of this iterator.
+     */
+    public FilteredIterator(final Iterator<S> source, final Function<S,T> 
mapper) {
+        this.source = source;
+        this.mapper = mapper;
+    }
+
+    /**
+     * Returns {@code true} if the iteration has more non-null elements.
+     */
+    @Override
+    public boolean hasNext() {
+        while (next == null) {
+            if (source.hasNext()) {
+                next = mapper.apply(source.next());
+            } else {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Returns the next non-null element in the iteration.
+     *
+     * @throws NoSuchElementException if the iteration has no more elements.
+     */
+    @Override
+    public T next() {
+        T element = next;
+        next = null;
+        while (element == null) {
+            // We will let the source throws NoSuchElementException.
+            element = mapper.apply(source.next());
+        }
+        return element;
+    }
+
+    /**
+     * Performs the given action for each remaining element.
+     */
+    @Override
+    public void forEachRemaining(final Consumer<? super T> action) {
+        T element = next;
+        if (element != null) {
+            next = null;
+            action.accept(element);
+        }
+        while (source.hasNext()) {
+            element = mapper.apply(source.next());
+            if (element != null) {
+                action.accept(element);
+            }
+        }
+    }
+}
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/LazySet.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/LazySet.java
index 5183689e4e..9035a15b69 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/LazySet.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/LazySet.java
@@ -25,7 +25,7 @@ import org.apache.sis.util.privy.SetOfUnknownSize;
 /**
  * An unmodifiable set built from an iterator, which will be filled only when 
needed.
  * This implementation does <strong>not</strong> check if all elements in the 
iterator
- * are really unique; we assume that this condition was already verified by 
the caller.
+ * are really unique. We assume that this condition was already verified by 
the caller.
  *
  * <p>Some usages for this class are to prepend some values before the 
elements given by the source {@code Iterable},
  * or to replace some values when they are loaded.</p>
@@ -35,6 +35,8 @@ import org.apache.sis.util.privy.SetOfUnknownSize;
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  *
+ * @see FilteredIterator
+ *
  * @param <E>  the type of elements in the set.
  */
 public abstract class LazySet<E> extends SetOfUnknownSize<E> {
@@ -66,11 +68,11 @@ public abstract class LazySet<E> extends 
SetOfUnknownSize<E> {
     }
 
     /**
-     * Creates the iterator which will provide the elements of this set before 
filtering.
+     * Creates the iterator which will provide the elements of this set.
      * This method will be invoked only when first needed and at most once, 
unless {@link #reload()} is invoked.
      * After creation, calls to {@link Iterator#next()} will also be done only 
when first needed.
      *
-     * @return iterator over the elements of this set before filtering.
+     * @return iterator over the elements of this set.
      */
     protected abstract Iterator<? extends E> createSourceIterator();
 
@@ -145,26 +147,13 @@ public abstract class LazySet<E> extends 
SetOfUnknownSize<E> {
     public final synchronized int size() {
         if (canPullMore()) {
             while (sourceIterator.hasNext()) {
-                cache(next(sourceIterator));
+                cache(sourceIterator.next());
             }
             sourceIterator = null;
         }
         return numCached;
     }
 
-    /**
-     * Returns the next element from the given iterator. Default 
implementation returns {@link Iterator#next()}.
-     * Subclasses may override if they need to apply additional processing. 
For example, this method can be used
-     * for skipping data, but this approach works only if we have the 
guarantee that another element exists after
-     * the skipped one (because {@code LazySet} will not invoke {@link 
Iterator#hasNext()} again).
-     *
-     * @param  it  the iterator from which to get a next value.
-     * @return the next value (may be {@code null}).
-     */
-    protected E next(final Iterator<? extends E> it) {
-        return it.next();
-    }
-
     /**
      * Caches a new element. This method is invoked by {@code LazySet} inside 
a synchronized block.
      * Subclasses could override this method if they want to substitute the 
given value by another value.
@@ -205,7 +194,7 @@ public abstract class LazySet<E> extends 
SetOfUnknownSize<E> {
         assert index <= numCached : index;
         if (index >= numCached) {
             if (canPullMore()) {
-                cache(next(sourceIterator));
+                cache(sourceIterator.next());
             } else {
                 throw new NoSuchElementException();
             }
diff --git 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/ConcurrentAuthorityFactoryTest.java
 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/ConcurrentAuthorityFactoryTest.java
index 32be48bacf..09e350c3a3 100644
--- 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/ConcurrentAuthorityFactoryTest.java
+++ 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/ConcurrentAuthorityFactoryTest.java
@@ -165,7 +165,7 @@ public final class ConcurrentAuthorityFactoryTest extends 
TestCase {
             assertTrue  (createdDAOs.get(1).isClosed(),         "Worker should 
be disposed.");
             assertTrue  (createdDAOs.get(0).isClosed(),         "Worker should 
be disposed.");
         }
-        // If the garbage collector didn't complete, report as a skipped test 
instead than a test failure.
+        // If the garbage collector didn't complete, report as a skipped test 
instead of a test failure.
         assumeTrue(r1 & r2, "The execution of 
ConcurrentAuthorityFactory.disposeExpired() could not complete.");
     }
 
diff --git 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/IdentifiedObjectFinderTest.java
 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/IdentifiedObjectFinderTest.java
index eb7371b579..671fb48832 100644
--- 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/IdentifiedObjectFinderTest.java
+++ 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/IdentifiedObjectFinderTest.java
@@ -20,7 +20,6 @@ import java.util.Map;
 import org.opengis.util.FactoryException;
 import org.opengis.referencing.IdentifiedObject;
 import org.opengis.referencing.crs.CRSAuthorityFactory;
-import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.crs.GeographicCRS;
 import org.apache.sis.referencing.crs.DefaultGeographicCRS;
 
@@ -73,7 +72,7 @@ public final class IdentifiedObjectFinderTest extends 
TestCase {
          * Same test as above, using a CRS without identifier.
          * The intent is to force a full scan.
          */
-        final CoordinateReferenceSystem search = new DefaultGeographicCRS(
+        final var search = new DefaultGeographicCRS(
                 Map.of(DefaultGeographicCRS.NAME_KEY, CRS84.getName()),
                 CRS84.getDatum(), CRS84.getDatumEnsemble(), 
CRS84.getCoordinateSystem());
         assertEqualsIgnoreMetadata(CRS84, search);              // Required 
condition for next test.
diff --git 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/sql/EPSGFactoryTest.java
 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/sql/EPSGFactoryTest.java
index bf8525cc65..68c7b489ff 100644
--- 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/sql/EPSGFactoryTest.java
+++ 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/sql/EPSGFactoryTest.java
@@ -887,7 +887,7 @@ public final class EPSGFactoryTest extends TestCaseWithLogs 
{
     public void testFindGeographic() throws FactoryException {
         final EPSGFactory factory = dataEPSG.factory();
         final IdentifiedObjectFinder finder = 
factory.newIdentifiedObjectFinder();
-        final DefaultGeographicCRS crs = (DefaultGeographicCRS) CRS.fromWKT(
+        final var crs = (DefaultGeographicCRS) CRS.fromWKT(
                 "GEOGCS[“WGS 84”,\n" +
                 "  DATUM[“WGS 84”,\n" +     // Use the alias instead of 
primary name for forcing a deeper search.
                 "    SPHEROID[“WGS 1984”, 6378137.0, 298.257223563]],\n" +  // 
Different name for forcing a deeper search.
@@ -904,29 +904,17 @@ public final class EPSGFactoryTest extends 
TestCaseWithLogs {
         
assertTrue(finder.find(crs.forConvention(AxesConvention.NORMALIZED)).isEmpty(),
                    "Should not find WGS84 because the axis order is not the 
same.");
         /*
-         * Ensure that the cache is empty.
+         * Performs a search based only on the name.
          */
         finder.setSearchDomain(IdentifiedObjectFinder.Domain.DECLARATION);
-        assertTrue(finder.find(crs).isEmpty(),
-                   "Should not find without a full scan, because the WKT 
contains no identifier " +
-                   "and the CRS name is ambiguous (more than one EPSG object 
have this name).");
-        /*
-         * Scan the database for searching the CRS.
-         */
-        finder.setSearchDomain(IdentifiedObjectFinder.Domain.ALL_DATASET);
         final IdentifiedObject found = finder.findSingleton(crs);
-        assertNotNull(found, "With full scan allowed, the CRS should be 
found.");
         assertEpsgNameAndIdentifierEqual("WGS 84", 4326, found);
         /*
-         * Search should behave as specified by `DECLARATION` contract even if 
the CRS is in the cache.
-         */
-        finder.setSearchDomain(IdentifiedObjectFinder.Domain.DECLARATION);
-        assertNull(finder.findSingleton(crs), "Should met `DECLARATION` 
contract.");
-        /*
-         * Should find the CRS without the need of a full scan, because of the 
cache.
+         * Scan the database for searching the CRS.
          */
-        finder.setSearchDomain(IdentifiedObjectFinder.Domain.ALL_DATASET);
-        assertSame(found, finder.findSingleton(crs), "The CRS should still in 
the cache.");
+        
finder.setSearchDomain(IdentifiedObjectFinder.Domain.EXHAUSTIVE_VALID_DATASET);
+        assertEpsgNameAndIdentifierEqual("WGS 84", 4326, 
finder.findSingleton(crs));
+        assertSame(found, finder.findSingleton(crs), "The CRS should be in the 
cache.");
         loggings.assertNoUnexpectedLog();
     }
 
diff --git 
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/InfoStatements.java
 
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/InfoStatements.java
index af7647a142..a0506facf7 100644
--- 
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/InfoStatements.java
+++ 
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/InfoStatements.java
@@ -55,6 +55,7 @@ import org.apache.sis.util.Localized;
 import org.apache.sis.util.Utilities;
 import org.apache.sis.util.Workaround;
 import org.apache.sis.util.privy.Constants;
+import org.apache.sis.util.collection.BackingStoreException;
 import org.apache.sis.io.wkt.Convention;
 import org.apache.sis.io.wkt.WKTFormat;
 import org.apache.sis.io.wkt.Warnings;
@@ -643,7 +644,12 @@ public class InfoStatements implements Localized, 
AutoCloseable {
             if (cached != null) {
                 return cached;
             }
-            result = findOrAddCRS(crs);
+            try {
+                result = findOrAddCRS(crs);
+            } catch (BackingStoreException e) {
+                // May be thrown by IdentifiedObjectFinder iterator.
+                throw e.unwrapOrRethrow(FactoryException.class);
+            }
             database.cacheOfSRID.put(crs, result.srid);
         }
         CommonExecutor.instance().submit((Runnable) result);
diff --git 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/privy/SetOfUnknownSize.java
 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/privy/SetOfUnknownSize.java
index 999ba40b18..c22f17689b 100644
--- 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/privy/SetOfUnknownSize.java
+++ 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/privy/SetOfUnknownSize.java
@@ -91,7 +91,7 @@ public abstract class SetOfUnknownSize<E> extends 
AbstractSet<E> {
     @Override
     public boolean removeAll(final Collection<?> c) {
         /*
-         * Do not invoke super.removeAll(c) even if isSizeKnown() returns 
'true' because we want to unconditionally
+         * Do not invoke super.removeAll(c) even if isSizeKnown() returns 
`true` because we want to unconditionally
          * iterate over the elements of the given collection. The reason is 
that this Set may compute the values in
          * a dynamic way and it is sometimes difficult to ensure that the 
values returned by this Set's iterator are
          * fully consistent with the values recognized by contains(Object) and 
remove(Object) methods. Furthermore,
@@ -173,7 +173,7 @@ public abstract class SetOfUnknownSize<E> extends 
AbstractSet<E> {
     @Override
     public boolean equals(final Object object) {
         /*
-         * Do not invoke super.equals(object) even if isSizeKnown() returns 
'true' because we want to unconditionally
+         * Do not invoke super.equals(object) even if isSizeKnown() returns 
`true` because we want to unconditionally
          * iterate over the elements of this Set. The reason is that this Set 
may compute the values dynamically and
          * it is sometimes difficult to ensure that this Set's iterator is 
fully consistent with the values recognized
          * by the contains(Object) method. For example, the iterator may 
return "EPSG:4326" while the contains(Object)
@@ -186,7 +186,7 @@ public abstract class SetOfUnknownSize<E> extends 
AbstractSet<E> {
         if (!(object instanceof Set<?>)) {
             return false;
         }
-        final Set<?> that = (Set<?>) object;
+        final var that = (Set<?>) object;
         int size = 0;
         for (final Iterator<E> it = iterator(); it.hasNext();) {
             if (!that.contains(it.next())) {
diff --git a/netbeans-project/nbproject/project.xml 
b/netbeans-project/nbproject/project.xml
index f578f8c466..b642dd80ff 100644
--- a/netbeans-project/nbproject/project.xml
+++ b/netbeans-project/nbproject/project.xml
@@ -34,6 +34,7 @@
             <word>Geopackage</word>
             <word>geospatial</word>
             <word>Molodensky</word>
+            <word>namespace</word>
             <word>transformative</word>
             <word>untiled</word>
         </spellchecker-wordlist>
diff --git 
a/optional/src/org.apache.sis.referencing.database/test/org/apache/sis/resources/embedded/Generator.java
 
b/optional/src/org.apache.sis.referencing.database/test/org/apache/sis/resources/embedded/Generator.java
index 95b646aaa1..89110202f0 100644
--- 
a/optional/src/org.apache.sis.referencing.database/test/org/apache/sis/resources/embedded/Generator.java
+++ 
b/optional/src/org.apache.sis.referencing.database/test/org/apache/sis/resources/embedded/Generator.java
@@ -126,7 +126,7 @@ final class Generator extends ScriptProvider {
      * Copies the <abbr>EPSG</abbr> terms of use from the {@code sis-epsg} 
module to this {@code sis-embedded-data} module.
      * If the <abbr>EPSG</abbr> data are not found, then this method does 
nothing.
      *
-     * <p>We copy those files ourselves instead than relying on {@code 
maven-resources-plugin}
+     * <p>We copy those files ourselves instead of relying on {@code 
maven-resources-plugin}
      * because a future version may combine more licenses in a single file.</p>
      */
     private void copyLicenseFiles() throws URISyntaxException, IOException {


Reply via email to