SOLR-13583: Impossible to delete a collection with the same name as an existing alias.
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index cc15c3a..5ff01e0 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -43,6 +43,9 @@
 
 * SOLR-13159: Fix atomic update encoding issue for UUID, enum, bool, and binary fields (Thomas Wockinger via Jason Gerlowski)
 
+* SOLR-13583: Impossible to delete a collection with the same name as an existing alias. This fixes also a bug in
+  REINDEXCOLLECTION when used with removeSource=true which could lead to a data loss. (ab)
+
 Improvements
 ----------------------
 
diff --git a/solr/core/src/java/org/apache/solr/cloud/api/collections/AddReplicaCmd.java b/solr/core/src/java/org/apache/solr/cloud/api/collections/AddReplicaCmd.java
index 08dc12a..2980030 100644
--- a/solr/core/src/java/org/apache/solr/cloud/api/collections/AddReplicaCmd.java
+++ b/solr/core/src/java/org/apache/solr/cloud/api/collections/AddReplicaCmd.java
@@ -27,6 +27,7 @@
 import static org.apache.solr.common.cloud.ZkStateReader.SHARD_ID_PROP;
 import static org.apache.solr.common.cloud.ZkStateReader.TLOG_REPLICAS;
 import static org.apache.solr.common.params.CollectionAdminParams.COLL_CONF;
+import static org.apache.solr.common.params.CollectionAdminParams.FOLLOW_ALIASES;
 import static org.apache.solr.common.params.CollectionAdminParams.WITH_COLLECTION;
 import static org.apache.solr.common.params.CollectionParams.CollectionAction.ADDREPLICA;
 import static org.apache.solr.common.params.CommonAdminParams.ASYNC;
@@ -96,9 +97,15 @@
     log.debug("addReplica() : {}", Utils.toJSONString(message));
 
     String extCollectionName = message.getStr(COLLECTION_PROP);
+    boolean followAliases = message.getBool(FOLLOW_ALIASES, false);
     String shard = message.getStr(SHARD_ID_PROP);
 
-    final String collectionName = ocmh.cloudManager.getClusterStateProvider().resolveSimpleAlias(extCollectionName);
+    final String collectionName;
+    if (followAliases) {
+      collectionName =  ocmh.cloudManager.getClusterStateProvider().resolveSimpleAlias(extCollectionName);
+    } else {
+      collectionName = extCollectionName;
+    }
 
     DocCollection coll = clusterState.getCollection(collectionName);
     if (coll == null) {
diff --git a/solr/core/src/java/org/apache/solr/cloud/api/collections/BackupCmd.java b/solr/core/src/java/org/apache/solr/cloud/api/collections/BackupCmd.java
index bd3bd3b..ceec4e2 100644
--- a/solr/core/src/java/org/apache/solr/cloud/api/collections/BackupCmd.java
+++ b/solr/core/src/java/org/apache/solr/cloud/api/collections/BackupCmd.java
@@ -18,6 +18,7 @@
 
 import static org.apache.solr.common.cloud.ZkStateReader.COLLECTION_PROP;
 import static org.apache.solr.common.cloud.ZkStateReader.CORE_NAME_PROP;
+import static org.apache.solr.common.params.CollectionAdminParams.FOLLOW_ALIASES;
 import static org.apache.solr.common.params.CommonAdminParams.ASYNC;
 import static org.apache.solr.common.params.CommonParams.NAME;
 
@@ -67,7 +68,13 @@
   @Override
   public void call(ClusterState state, ZkNodeProps message, NamedList results) throws Exception {
     String extCollectionName = message.getStr(COLLECTION_PROP);
-    String collectionName = ocmh.cloudManager.getClusterStateProvider().resolveSimpleAlias(extCollectionName);
+    boolean followAliases = message.getBool(FOLLOW_ALIASES, false);
+    String collectionName;
+    if (followAliases) {
+      collectionName = ocmh.cloudManager.getClusterStateProvider().resolveSimpleAlias(extCollectionName);
+    } else {
+      collectionName = extCollectionName;
+    }
     String backupName = message.getStr(NAME);
     String repo = message.getStr(CoreAdminParams.BACKUP_REPOSITORY);
 
diff --git a/solr/core/src/java/org/apache/solr/cloud/api/collections/CreateCollectionCmd.java b/solr/core/src/java/org/apache/solr/cloud/api/collections/CreateCollectionCmd.java
index dfd5a21..447ec5e 100644
--- a/solr/core/src/java/org/apache/solr/cloud/api/collections/CreateCollectionCmd.java
+++ b/solr/core/src/java/org/apache/solr/cloud/api/collections/CreateCollectionCmd.java
@@ -112,7 +112,7 @@
     final boolean waitForFinalState = message.getBool(WAIT_FOR_FINAL_STATE, false);
     final String alias = message.getStr(ALIAS, collectionName);
     log.info("Create collection {}", collectionName);
-    if (clusterState.hasCollection(collectionName) || aliases.hasAlias(collectionName)) {
+    if (clusterState.hasCollection(collectionName)) {
       throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "collection already exists: " + collectionName);
     }
     if (aliases.hasAlias(collectionName)) {
diff --git a/solr/core/src/java/org/apache/solr/cloud/api/collections/CreateShardCmd.java b/solr/core/src/java/org/apache/solr/cloud/api/collections/CreateShardCmd.java
index ffe9890..08d27e5 100644
--- a/solr/core/src/java/org/apache/solr/cloud/api/collections/CreateShardCmd.java
+++ b/solr/core/src/java/org/apache/solr/cloud/api/collections/CreateShardCmd.java
@@ -39,6 +39,7 @@
 import static org.apache.solr.common.cloud.ZkStateReader.REPLICATION_FACTOR;
 import static org.apache.solr.common.cloud.ZkStateReader.SHARD_ID_PROP;
 import static org.apache.solr.common.cloud.ZkStateReader.TLOG_REPLICAS;
+import static org.apache.solr.common.params.CollectionAdminParams.FOLLOW_ALIASES;
 import static org.apache.solr.common.params.CommonAdminParams.ASYNC;
 
 public class CreateShardCmd implements OverseerCollectionMessageHandler.Cmd {
@@ -59,7 +60,13 @@
     if (extCollectionName == null || sliceName == null)
       throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "'collection' and 'shard' are required parameters");
 
-    String collectionName = ocmh.cloudManager.getClusterStateProvider().resolveSimpleAlias(extCollectionName);
+    boolean followAliases = message.getBool(FOLLOW_ALIASES, false);
+    String collectionName;
+    if (followAliases) {
+      collectionName = ocmh.cloudManager.getClusterStateProvider().resolveSimpleAlias(extCollectionName);
+    } else {
+      collectionName = extCollectionName;
+    }
     DocCollection collection = clusterState.getCollection(collectionName);
 
     int numNrtReplicas = message.getInt(NRT_REPLICAS, message.getInt(REPLICATION_FACTOR, collection.getInt(NRT_REPLICAS, collection.getInt(REPLICATION_FACTOR, 1))));
diff --git a/solr/core/src/java/org/apache/solr/cloud/api/collections/CreateSnapshotCmd.java b/solr/core/src/java/org/apache/solr/cloud/api/collections/CreateSnapshotCmd.java
index d761a88..abc3597 100644
--- a/solr/core/src/java/org/apache/solr/cloud/api/collections/CreateSnapshotCmd.java
+++ b/solr/core/src/java/org/apache/solr/cloud/api/collections/CreateSnapshotCmd.java
@@ -18,6 +18,7 @@
 
 import static org.apache.solr.common.cloud.ZkStateReader.COLLECTION_PROP;
 import static org.apache.solr.common.cloud.ZkStateReader.CORE_NAME_PROP;
+import static org.apache.solr.common.params.CollectionAdminParams.FOLLOW_ALIASES;
 import static org.apache.solr.common.params.CommonAdminParams.ASYNC;
 import static org.apache.solr.common.params.CommonParams.NAME;
 
@@ -66,7 +67,14 @@
   @Override
   public void call(ClusterState state, ZkNodeProps message, NamedList results) throws Exception {
     String extCollectionName =  message.getStr(COLLECTION_PROP);
-    String collectionName = ocmh.zkStateReader.getAliases().resolveSimpleAlias(extCollectionName);
+    boolean followAliases = message.getBool(FOLLOW_ALIASES, false);
+
+    String collectionName;
+    if (followAliases) {
+      collectionName = ocmh.zkStateReader.getAliases().resolveSimpleAlias(extCollectionName);
+    } else {
+      collectionName = extCollectionName;
+    }
 
     String commitName =  message.getStr(CoreAdminParams.COMMIT_NAME);
     String asyncId = message.getStr(ASYNC);
diff --git a/solr/core/src/java/org/apache/solr/cloud/api/collections/DeleteCollectionCmd.java b/solr/core/src/java/org/apache/solr/cloud/api/collections/DeleteCollectionCmd.java
index 2054258..97c6a98 100644
--- a/solr/core/src/java/org/apache/solr/cloud/api/collections/DeleteCollectionCmd.java
+++ b/solr/core/src/java/org/apache/solr/cloud/api/collections/DeleteCollectionCmd.java
@@ -19,6 +19,7 @@
 package org.apache.solr.cloud.api.collections;
 
 import static org.apache.solr.common.params.CollectionAdminParams.COLOCATED_WITH;
+import static org.apache.solr.common.params.CollectionAdminParams.FOLLOW_ALIASES;
 import static org.apache.solr.common.params.CollectionAdminParams.WITH_COLLECTION;
 import static org.apache.solr.common.params.CollectionParams.CollectionAction.DELETE;
 import static org.apache.solr.common.params.CommonAdminParams.ASYNC;
@@ -76,10 +77,17 @@
       zkStateReader.aliasesManager.update(); // aliases may have been stale; get latest from ZK
     }
 
-    List<String> aliasReferences = checkAliasReference(zkStateReader, extCollection);
+    boolean followAliases = message.getBool(FOLLOW_ALIASES, false);
+    List<String> aliasReferences = checkAliasReference(zkStateReader, extCollection, followAliases);
 
     Aliases aliases = zkStateReader.getAliases();
-    String collection = aliases.resolveSimpleAlias(extCollection);
+
+    String collection;
+    if (followAliases) {
+      collection = aliases.resolveSimpleAlias(extCollection);
+    } else {
+      collection = extCollection;
+    }
 
     checkNotColocatedWith(zkStateReader, collection);
 
@@ -118,8 +126,9 @@
 
       Set<String> okayExceptions = new HashSet<>(1);
       okayExceptions.add(NonExistentCoreException.class.getName());
+      ZkNodeProps internalMsg = message.plus(NAME, collection);
 
-      List<Replica> failedReplicas = ocmh.collectionCmd(message, params, results, null, asyncId, okayExceptions);
+      List<Replica> failedReplicas = ocmh.collectionCmd(internalMsg, params, results, null, asyncId, okayExceptions);
       for (Replica failedReplica : failedReplicas) {
         boolean isSharedFS = failedReplica.getBool(ZkStateReader.SHARED_STORAGE_PROP, false) && failedReplica.get("dataDir") != null;
         if (isSharedFS) {
@@ -185,14 +194,14 @@
   }
 
   // This method returns the single collection aliases to delete, if present, or null
-  private List<String> checkAliasReference(ZkStateReader zkStateReader, String extCollection) throws Exception {
+  private List<String> checkAliasReference(ZkStateReader zkStateReader, String extCollection, boolean followAliases) throws Exception {
     Aliases aliases = zkStateReader.getAliases();
-    List<String> aliasesRefs = referencedByAlias(extCollection, aliases);
+    List<String> aliasesRefs = referencedByAlias(extCollection, aliases, followAliases);
     List<String> aliasesToDelete = new ArrayList<>();
     if (aliasesRefs.size() > 0) {
       zkStateReader.aliasesManager.update(); // aliases may have been stale; get latest from ZK
       aliases = zkStateReader.getAliases();
-      aliasesRefs = referencedByAlias(extCollection, aliases);
+      aliasesRefs = referencedByAlias(extCollection, aliases, followAliases);
       if (aliasesRefs.size() > 0) {
         for (String alias : aliasesRefs) {
           // for back-compat in 8.x we don't automatically remove other
@@ -209,11 +218,12 @@
     return aliasesToDelete;
   }
 
-  public static List<String> referencedByAlias(String extCollection, Aliases aliases) throws IllegalArgumentException {
+  public static List<String> referencedByAlias(String extCollection, Aliases aliases, boolean followAliases) throws IllegalArgumentException {
     Objects.requireNonNull(aliases);
     // this quickly produces error if the name is a complex alias
-    String collection = aliases.resolveSimpleAlias(extCollection);
+    String collection = followAliases ? aliases.resolveSimpleAlias(extCollection) : extCollection;
     return aliases.getCollectionAliasListMap().entrySet().stream()
+        .filter(e -> !e.getKey().equals(collection))
         .filter(e -> e.getValue().contains(collection) || e.getValue().contains(extCollection))
         .map(Map.Entry::getKey) // alias name
         .collect(Collectors.toList());
diff --git a/solr/core/src/java/org/apache/solr/cloud/api/collections/DeleteReplicaCmd.java b/solr/core/src/java/org/apache/solr/cloud/api/collections/DeleteReplicaCmd.java
index d999a6f..a6b45b5 100644
--- a/solr/core/src/java/org/apache/solr/cloud/api/collections/DeleteReplicaCmd.java
+++ b/solr/core/src/java/org/apache/solr/cloud/api/collections/DeleteReplicaCmd.java
@@ -20,6 +20,7 @@
 import static org.apache.solr.common.cloud.ZkStateReader.REPLICA_PROP;
 import static org.apache.solr.common.cloud.ZkStateReader.SHARD_ID_PROP;
 import static org.apache.solr.common.params.CollectionAdminParams.COUNT_PROP;
+import static org.apache.solr.common.params.CollectionAdminParams.FOLLOW_ALIASES;
 import static org.apache.solr.common.params.CommonAdminParams.ASYNC;
 
 import java.lang.invoke.MethodHandles;
@@ -85,7 +86,13 @@
     String shard = message.getStr(SHARD_ID_PROP);
     String replicaName = message.getStr(REPLICA_PROP);
 
-    String collectionName = ocmh.cloudManager.getClusterStateProvider().resolveSimpleAlias(extCollectionName);
+    boolean followAliases = message.getBool(FOLLOW_ALIASES, false);
+    String collectionName;
+    if (followAliases) {
+      collectionName = ocmh.cloudManager.getClusterStateProvider().resolveSimpleAlias(extCollectionName);
+    } else {
+      collectionName = extCollectionName;
+    }
 
     DocCollection coll = clusterState.getCollection(collectionName);
     Slice slice = coll.getSlice(shard);
diff --git a/solr/core/src/java/org/apache/solr/cloud/api/collections/DeleteShardCmd.java b/solr/core/src/java/org/apache/solr/cloud/api/collections/DeleteShardCmd.java
index e38aa4a..41b2c48 100644
--- a/solr/core/src/java/org/apache/solr/cloud/api/collections/DeleteShardCmd.java
+++ b/solr/core/src/java/org/apache/solr/cloud/api/collections/DeleteShardCmd.java
@@ -20,6 +20,7 @@
 import static org.apache.solr.common.cloud.ZkStateReader.COLLECTION_PROP;
 import static org.apache.solr.common.cloud.ZkStateReader.NODE_NAME_PROP;
 import static org.apache.solr.common.cloud.ZkStateReader.SHARD_ID_PROP;
+import static org.apache.solr.common.params.CollectionAdminParams.FOLLOW_ALIASES;
 import static org.apache.solr.common.params.CollectionParams.CollectionAction.DELETEREPLICA;
 import static org.apache.solr.common.params.CollectionParams.CollectionAction.DELETESHARD;
 import static org.apache.solr.common.params.CommonAdminParams.ASYNC;
@@ -65,7 +66,13 @@
     String extCollectionName = message.getStr(ZkStateReader.COLLECTION_PROP);
     String sliceId = message.getStr(ZkStateReader.SHARD_ID_PROP);
 
-    String collectionName = ocmh.cloudManager.getClusterStateProvider().resolveSimpleAlias(extCollectionName);
+    boolean followAliases = message.getBool(FOLLOW_ALIASES, false);
+    String collectionName;
+    if (followAliases) {
+      collectionName = ocmh.cloudManager.getClusterStateProvider().resolveSimpleAlias(extCollectionName);
+    } else {
+      collectionName = extCollectionName;
+    }
 
     log.info("Delete shard invoked");
     Slice slice = clusterState.getCollection(collectionName).getSlice(sliceId);
diff --git a/solr/core/src/java/org/apache/solr/cloud/api/collections/DeleteSnapshotCmd.java b/solr/core/src/java/org/apache/solr/cloud/api/collections/DeleteSnapshotCmd.java
index 88881cf..634692d 100644
--- a/solr/core/src/java/org/apache/solr/cloud/api/collections/DeleteSnapshotCmd.java
+++ b/solr/core/src/java/org/apache/solr/cloud/api/collections/DeleteSnapshotCmd.java
@@ -18,6 +18,7 @@
 
 import static org.apache.solr.common.cloud.ZkStateReader.COLLECTION_PROP;
 import static org.apache.solr.common.cloud.ZkStateReader.CORE_NAME_PROP;
+import static org.apache.solr.common.params.CollectionAdminParams.FOLLOW_ALIASES;
 import static org.apache.solr.common.params.CommonAdminParams.ASYNC;
 import static org.apache.solr.common.params.CommonParams.NAME;
 
@@ -64,7 +65,13 @@
   @Override
   public void call(ClusterState state, ZkNodeProps message, NamedList results) throws Exception {
     String extCollectionName =  message.getStr(COLLECTION_PROP);
-    String collectionName = ocmh.zkStateReader.getAliases().resolveSimpleAlias(extCollectionName);
+    boolean followAliases = message.getBool(FOLLOW_ALIASES, false);
+    String collectionName;
+    if (followAliases) {
+      collectionName = ocmh.zkStateReader.getAliases().resolveSimpleAlias(extCollectionName);
+    } else {
+      collectionName = extCollectionName;
+    }
     String commitName =  message.getStr(CoreAdminParams.COMMIT_NAME);
     String asyncId = message.getStr(ASYNC);
     NamedList shardRequestResults = new NamedList();
diff --git a/solr/core/src/java/org/apache/solr/cloud/api/collections/MigrateCmd.java b/solr/core/src/java/org/apache/solr/cloud/api/collections/MigrateCmd.java
index 55210f1..2ecf5f8 100644
--- a/solr/core/src/java/org/apache/solr/cloud/api/collections/MigrateCmd.java
+++ b/solr/core/src/java/org/apache/solr/cloud/api/collections/MigrateCmd.java
@@ -54,6 +54,7 @@
 import static org.apache.solr.common.cloud.ZkStateReader.COLLECTION_PROP;
 import static org.apache.solr.common.cloud.ZkStateReader.NRT_REPLICAS;
 import static org.apache.solr.common.cloud.ZkStateReader.SHARD_ID_PROP;
+import static org.apache.solr.common.params.CollectionAdminParams.FOLLOW_ALIASES;
 import static org.apache.solr.common.params.CollectionParams.CollectionAction.ADDREPLICA;
 import static org.apache.solr.common.params.CollectionParams.CollectionAction.CREATE;
 import static org.apache.solr.common.params.CollectionParams.CollectionAction.DELETE;
@@ -79,8 +80,16 @@
     String extTargetCollectionName = message.getStr("target.collection");
     int timeout = message.getInt("forward.timeout", 10 * 60) * 1000;
 
-    String sourceCollectionName = ocmh.cloudManager.getClusterStateProvider().resolveSimpleAlias(extSourceCollectionName);
-    String targetCollectionName = ocmh.cloudManager.getClusterStateProvider().resolveSimpleAlias(extTargetCollectionName);
+    boolean followAliases = message.getBool(FOLLOW_ALIASES, false);
+    String sourceCollectionName;
+    String targetCollectionName;
+    if (followAliases) {
+      sourceCollectionName = ocmh.cloudManager.getClusterStateProvider().resolveSimpleAlias(extSourceCollectionName);
+      targetCollectionName = ocmh.cloudManager.getClusterStateProvider().resolveSimpleAlias(extTargetCollectionName);
+    } else {
+      sourceCollectionName = extSourceCollectionName;
+      targetCollectionName = extTargetCollectionName;
+    }
 
     DocCollection sourceCollection = clusterState.getCollection(sourceCollectionName);
     if (sourceCollection == null) {
diff --git a/solr/core/src/java/org/apache/solr/cloud/api/collections/MoveReplicaCmd.java b/solr/core/src/java/org/apache/solr/cloud/api/collections/MoveReplicaCmd.java
index fc39b9d..4e462f6 100644
--- a/solr/core/src/java/org/apache/solr/cloud/api/collections/MoveReplicaCmd.java
+++ b/solr/core/src/java/org/apache/solr/cloud/api/collections/MoveReplicaCmd.java
@@ -48,6 +48,7 @@
 import static org.apache.solr.common.cloud.ZkStateReader.COLLECTION_PROP;
 import static org.apache.solr.common.cloud.ZkStateReader.REPLICA_PROP;
 import static org.apache.solr.common.cloud.ZkStateReader.SHARD_ID_PROP;
+import static org.apache.solr.common.params.CollectionAdminParams.FOLLOW_ALIASES;
 import static org.apache.solr.common.params.CommonAdminParams.ASYNC;
 import static org.apache.solr.common.params.CommonAdminParams.IN_PLACE_MOVE;
 import static org.apache.solr.common.params.CommonAdminParams.TIMEOUT;
@@ -80,7 +81,13 @@
 
     String async = message.getStr(ASYNC);
 
-    String collection = ocmh.cloudManager.getClusterStateProvider().resolveSimpleAlias(extCollection);
+    boolean followAliases = message.getBool(FOLLOW_ALIASES, false);
+    String collection;
+    if (followAliases) {
+      collection = ocmh.cloudManager.getClusterStateProvider().resolveSimpleAlias(extCollection);
+    } else {
+      collection = extCollection;
+    }
 
     DocCollection coll = clusterState.getCollection(collection);
     if (coll == null) {
diff --git a/solr/core/src/java/org/apache/solr/cloud/api/collections/ReindexCollectionCmd.java b/solr/core/src/java/org/apache/solr/cloud/api/collections/ReindexCollectionCmd.java
index 57e7a62..da9bacb 100644
--- a/solr/core/src/java/org/apache/solr/cloud/api/collections/ReindexCollectionCmd.java
+++ b/solr/core/src/java/org/apache/solr/cloud/api/collections/ReindexCollectionCmd.java
@@ -64,6 +64,8 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import static org.apache.solr.common.params.CollectionAdminParams.FOLLOW_ALIASES;
+
 /**
  * Reindex a collection, usually in order to change the index schema.
  * <p>WARNING: Reindexing is potentially a lossy operation - some indexed data that is not available as
@@ -178,7 +180,13 @@
     if (extCollection == null) {
       throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Source collection name must be specified");
     }
-    String collection = ocmh.cloudManager.getClusterStateProvider().resolveSimpleAlias(extCollection);
+    boolean followAliases = message.getBool(FOLLOW_ALIASES, false);
+    String collection;
+    if (followAliases) {
+      collection = ocmh.cloudManager.getClusterStateProvider().resolveSimpleAlias(extCollection);
+    } else {
+      collection = extCollection;
+    }
     if (!clusterState.hasCollection(collection)) {
       throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Source collection name must exist");
     }
@@ -186,8 +194,9 @@
     if (target == null) {
       target = collection;
     } else {
-      // resolve aliases
-      target = ocmh.cloudManager.getClusterStateProvider().resolveSimpleAlias(target);
+      if (followAliases) {
+        target = ocmh.cloudManager.getClusterStateProvider().resolveSimpleAlias(target);
+      }
     }
     boolean sameTarget = target.equals(collection) || target.equals(extCollection);
     boolean removeSource = message.getBool(REMOVE_SOURCE, false);
@@ -466,6 +475,7 @@
         cmd = new ZkNodeProps(
             Overseer.QUEUE_OPERATION, CollectionParams.CollectionAction.DELETE.toLower(),
             CommonParams.NAME, collection,
+            FOLLOW_ALIASES, "false",
             CoreAdminParams.DELETE_METRICS_HISTORY, "true"
         );
         cmdResults = new NamedList<>();
@@ -770,6 +780,7 @@
       ZkNodeProps cmd = new ZkNodeProps(
           Overseer.QUEUE_OPERATION, CollectionParams.CollectionAction.DELETE.toLower(),
           CommonParams.NAME, targetCollection,
+          FOLLOW_ALIASES, "false",
           CoreAdminParams.DELETE_METRICS_HISTORY, "true"
       );
       ocmh.commandMap.get(CollectionParams.CollectionAction.DELETE).call(clusterState, cmd, cmdResults);
@@ -782,6 +793,7 @@
       ZkNodeProps cmd = new ZkNodeProps(
           Overseer.QUEUE_OPERATION, CollectionParams.CollectionAction.DELETE.toLower(),
           CommonParams.NAME, chkCollection,
+          FOLLOW_ALIASES, "false",
           CoreAdminParams.DELETE_METRICS_HISTORY, "true"
       );
       cmdResults = new NamedList<>();
diff --git a/solr/core/src/java/org/apache/solr/cloud/api/collections/RenameCmd.java b/solr/core/src/java/org/apache/solr/cloud/api/collections/RenameCmd.java
index 2a33e49..7296f6c 100644
--- a/solr/core/src/java/org/apache/solr/cloud/api/collections/RenameCmd.java
+++ b/solr/core/src/java/org/apache/solr/cloud/api/collections/RenameCmd.java
@@ -29,6 +29,8 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import static org.apache.solr.common.params.CollectionAdminParams.FOLLOW_ALIASES;
+
 /**
  *
  */
@@ -55,7 +57,13 @@
     }
     Aliases aliases = ocmh.zkStateReader.getAliases();
 
-    String collectionName = aliases.resolveSimpleAlias(extCollectionName);
+    boolean followAliases = message.getBool(FOLLOW_ALIASES, false);
+    String collectionName;
+    if (followAliases) {
+      collectionName = aliases.resolveSimpleAlias(extCollectionName);
+    } else {
+      collectionName = extCollectionName;
+    }
     if (!state.hasCollection(collectionName)) {
       throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "source collection '" + collectionName + "' not found.");
     }
@@ -65,6 +73,5 @@
     }
 
     ocmh.zkStateReader.aliasesManager.applyModificationAndExportToZk(a -> a.cloneWithRename(extCollectionName, target));
-
   }
 }
diff --git a/solr/core/src/java/org/apache/solr/cloud/api/collections/SplitShardCmd.java b/solr/core/src/java/org/apache/solr/cloud/api/collections/SplitShardCmd.java
index e36c76f..dd86ec4 100644
--- a/solr/core/src/java/org/apache/solr/cloud/api/collections/SplitShardCmd.java
+++ b/solr/core/src/java/org/apache/solr/cloud/api/collections/SplitShardCmd.java
@@ -73,6 +73,7 @@
 import static org.apache.solr.common.cloud.ZkStateReader.COLLECTION_PROP;
 import static org.apache.solr.common.cloud.ZkStateReader.REPLICA_TYPE;
 import static org.apache.solr.common.cloud.ZkStateReader.SHARD_ID_PROP;
+import static org.apache.solr.common.params.CollectionAdminParams.FOLLOW_ALIASES;
 import static org.apache.solr.common.params.CollectionParams.CollectionAction.ADDREPLICA;
 import static org.apache.solr.common.params.CollectionParams.CollectionAction.CREATESHARD;
 import static org.apache.solr.common.params.CollectionParams.CollectionAction.DELETESHARD;
@@ -111,7 +112,13 @@
 
     String extCollectionName = message.getStr(CoreAdminParams.COLLECTION);
 
-    String collectionName = ocmh.cloudManager.getClusterStateProvider().resolveSimpleAlias(extCollectionName);
+    boolean followAliases = message.getBool(FOLLOW_ALIASES, false);
+    String collectionName;
+    if (followAliases) {
+      collectionName = ocmh.cloudManager.getClusterStateProvider().resolveSimpleAlias(extCollectionName);
+    } else {
+      collectionName = extCollectionName;
+    }
 
     log.debug("Split shard invoked: {}", message);
     ZkStateReader zkStateReader = ocmh.zkStateReader;
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java
index 07ec42a..2cf2cb2 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java
@@ -141,6 +141,7 @@
 import static org.apache.solr.common.params.CollectionAdminParams.COLLECTION;
 import static org.apache.solr.common.params.CollectionAdminParams.COLL_CONF;
 import static org.apache.solr.common.params.CollectionAdminParams.COUNT_PROP;
+import static org.apache.solr.common.params.CollectionAdminParams.FOLLOW_ALIASES;
 import static org.apache.solr.common.params.CollectionAdminParams.PROPERTY_NAME;
 import static org.apache.solr.common.params.CollectionAdminParams.PROPERTY_VALUE;
 import static org.apache.solr.common.params.CollectionAdminParams.WITH_COLLECTION;
@@ -540,11 +541,20 @@
           .getColStatus(rsp.getValues());
       return null;
     }),
-    DELETE_OP(DELETE, (req, rsp, h) -> copy(req.getParams().required(), null, NAME)),
+    DELETE_OP(DELETE, (req, rsp, h) -> {
+      Map<String, Object> map = copy(req.getParams().required(), null, NAME);
+      return copy(req.getParams(), map, FOLLOW_ALIASES);
+    }),
+    // XXX should this command support followAliases?
+    RELOAD_OP(RELOAD, (req, rsp, h) -> {
+      Map<String, Object> map = copy(req.getParams().required(), null, NAME);
+      return copy(req.getParams(), map);
+    }),
 
-    RELOAD_OP(RELOAD, (req, rsp, h) -> copy(req.getParams().required(), null, NAME)),
-
-    RENAME_OP(RENAME, (req, rsp, h) -> copy(req.getParams().required(), null, NAME, CollectionAdminParams.TARGET)),
+    RENAME_OP(RENAME, (req, rsp, h) -> {
+      Map<String, Object> map = copy(req.getParams().required(), null, NAME, CollectionAdminParams.TARGET);
+      return copy(req.getParams(), map, FOLLOW_ALIASES);
+    }),
 
     REINDEXCOLLECTION_OP(REINDEXCOLLECTION, (req, rsp, h) -> {
       Map<String, Object> m = copy(req.getParams().required(), null, NAME);
@@ -567,7 +577,8 @@
           STATE_FORMAT,
           CommonParams.ROWS,
           CommonParams.Q,
-          CommonParams.FL);
+          CommonParams.FL,
+          FOLLOW_ALIASES);
       if (req.getParams().get("collection." + ZkStateReader.CONFIGNAME_PROP) != null) {
         m.put(ZkStateReader.CONFIGNAME_PROP, req.getParams().get("collection." + ZkStateReader.CONFIGNAME_PROP));
       }
@@ -744,7 +755,8 @@
           TIMING,
           SPLIT_METHOD,
           NUM_SUB_SHARDS,
-          SPLIT_FUZZ);
+          SPLIT_FUZZ,
+          FOLLOW_ALIASES);
       return copyPropertiesWithPrefix(req.getParams(), map, COLL_PROP_PREFIX);
     }),
     DELETESHARD_OP(DELETESHARD, (req, rsp, h) -> {
@@ -755,7 +767,8 @@
           DELETE_INDEX,
           DELETE_DATA_DIR,
           DELETE_INSTANCE_DIR,
-          DELETE_METRICS_HISTORY);
+          DELETE_METRICS_HISTORY,
+          FOLLOW_ALIASES);
       return map;
     }),
     FORCELEADER_OP(FORCELEADER, (req, rsp, h) -> {
@@ -768,7 +781,11 @@
           SHARD_ID_PROP);
       ClusterState clusterState = h.coreContainer.getZkController().getClusterState();
       final String newShardName = SolrIdentifierValidator.validateShardName(req.getParams().get(SHARD_ID_PROP));
-      if (!ImplicitDocRouter.NAME.equals(((Map) clusterState.getCollection(req.getParams().get(COLLECTION_PROP)).get(DOC_ROUTER)).get(NAME)))
+      boolean followAliases = req.getParams().getBool(FOLLOW_ALIASES, false);
+      String extCollectionName = req.getParams().get(COLLECTION_PROP);
+      String collectionName = followAliases ? h.coreContainer.getZkController().getZkStateReader()
+          .getAliases().resolveSimpleAlias(extCollectionName) : extCollectionName;
+      if (!ImplicitDocRouter.NAME.equals(((Map) clusterState.getCollection(collectionName).get(DOC_ROUTER)).get(NAME)))
         throw new SolrException(ErrorCode.BAD_REQUEST, "shards can be added only to 'implicit' collections");
       copy(req.getParams(), map,
           REPLICATION_FACTOR,
@@ -776,7 +793,8 @@
           TLOG_REPLICAS,
           PULL_REPLICAS,
           CREATE_NODE_SET,
-          WAIT_FOR_FINAL_STATE);
+          WAIT_FOR_FINAL_STATE,
+          FOLLOW_ALIASES);
       return copyPropertiesWithPrefix(req.getParams(), map, COLL_PROP_PREFIX);
     }),
     DELETEREPLICA_OP(DELETEREPLICA, (req, rsp, h) -> {
@@ -788,13 +806,14 @@
           DELETE_DATA_DIR,
           DELETE_INSTANCE_DIR,
           DELETE_METRICS_HISTORY,
-              COUNT_PROP, REPLICA_PROP,
-              SHARD_ID_PROP,
-          ONLY_IF_DOWN);
+          COUNT_PROP, REPLICA_PROP,
+          SHARD_ID_PROP,
+          ONLY_IF_DOWN,
+          FOLLOW_ALIASES);
     }),
     MIGRATE_OP(MIGRATE, (req, rsp, h) -> {
       Map<String, Object> map = copy(req.getParams().required(), null, COLLECTION_PROP, "split.key", "target.collection");
-      return copy(req.getParams(), map, "forward.timeout");
+      return copy(req.getParams(), map, "forward.timeout", FOLLOW_ALIASES);
     }),
     ADDROLE_OP(ADDROLE, (req, rsp, h) -> {
       Map<String, Object> map = copy(req.getParams().required(), null, "role", "node");
@@ -914,7 +933,8 @@
           NRT_REPLICAS,
           TLOG_REPLICAS,
           PULL_REPLICAS,
-          CREATE_NODE_SET);
+          CREATE_NODE_SET,
+          FOLLOW_ALIASES);
       return copyPropertiesWithPrefix(req.getParams(), props, COLL_PROP_PREFIX);
     }),
     OVERSEERSTATUS_OP(OVERSEERSTATUS, (req, rsp, h) -> (Map) new LinkedHashMap<>()),
@@ -976,6 +996,7 @@
       }
       return map;
     }),
+    // XXX should this command support followAliases?
     DELETEREPLICAPROP_OP(DELETEREPLICAPROP, (req, rsp, h) -> {
       Map<String, Object> map = copy(req.getParams().required(), null,
           COLLECTION_PROP,
@@ -984,6 +1005,7 @@
           REPLICA_PROP);
       return copy(req.getParams(), map, PROPERTY_PROP);
     }),
+    // XXX should this command support followAliases?
     BALANCESHARDUNIQUE_OP(BALANCESHARDUNIQUE, (req, rsp, h) -> {
       Map<String, Object> map = copy(req.getParams().required(), null,
           COLLECTION_PROP,
@@ -1006,6 +1028,7 @@
       new RebalanceLeaders(req, rsp, h).execute();
       return null;
     }),
+    // XXX should this command support followAliases?
     MODIFYCOLLECTION_OP(MODIFYCOLLECTION, (req, rsp, h) -> {
       Map<String, Object> m = copy(req.getParams(), null, CollectionAdminRequest.MODIFIABLE_COLLECTION_PROPERTIES);
       copyPropertiesWithPrefix(req.getParams(), m, COLL_PROP_PREFIX);
@@ -1035,8 +1058,9 @@
       req.getParams().required().check(NAME, COLLECTION_PROP);
 
       String extCollectionName = req.getParams().get(COLLECTION_PROP);
-      String collectionName = h.coreContainer.getZkController().getZkStateReader()
-          .getAliases().resolveSimpleAlias(extCollectionName);
+      boolean followAliases = req.getParams().getBool(FOLLOW_ALIASES, false);
+      String collectionName = followAliases ? h.coreContainer.getZkController().getZkStateReader()
+          .getAliases().resolveSimpleAlias(extCollectionName) : extCollectionName;
       ClusterState clusterState = h.coreContainer.getZkController().getClusterState();
       if (!clusterState.hasCollection(collectionName)) {
         throw new SolrException(ErrorCode.BAD_REQUEST, "Collection '" + collectionName + "' does not exist, no action taken.");
@@ -1072,7 +1096,7 @@
         throw new SolrException(ErrorCode.BAD_REQUEST, "Unknown index backup strategy " + strategy);
       }
 
-      Map<String, Object> params = copy(req.getParams(), null, NAME, COLLECTION_PROP, CoreAdminParams.COMMIT_NAME);
+      Map<String, Object> params = copy(req.getParams(), null, NAME, COLLECTION_PROP, FOLLOW_ALIASES, CoreAdminParams.COMMIT_NAME);
       params.put(CoreAdminParams.BACKUP_LOCATION, location);
       params.put(CollectionAdminParams.INDEX_BACKUP_STRATEGY, strategy);
       return params;
@@ -1139,8 +1163,9 @@
       req.getParams().required().check(COLLECTION_PROP, CoreAdminParams.COMMIT_NAME);
 
       String extCollectionName = req.getParams().get(COLLECTION_PROP);
-      String collectionName = h.coreContainer.getZkController().getZkStateReader()
-          .getAliases().resolveSimpleAlias(extCollectionName);
+      boolean followAliases = req.getParams().getBool(FOLLOW_ALIASES, false);
+      String collectionName = followAliases ? h.coreContainer.getZkController().getZkStateReader()
+          .getAliases().resolveSimpleAlias(extCollectionName) : extCollectionName;
       String commitName = req.getParams().get(CoreAdminParams.COMMIT_NAME);
       ClusterState clusterState = h.coreContainer.getZkController().getClusterState();
       if (!clusterState.hasCollection(collectionName)) {
@@ -1154,7 +1179,7 @@
                 + collectionName + "', no action taken.");
       }
 
-      Map<String, Object> params = copy(req.getParams(), null, COLLECTION_PROP, CoreAdminParams.COMMIT_NAME);
+      Map<String, Object> params = copy(req.getParams(), null, COLLECTION_PROP, FOLLOW_ALIASES, CoreAdminParams.COMMIT_NAME);
       return params;
     }),
     DELETESNAPSHOT_OP(DELETESNAPSHOT, (req, rsp, h) -> {
@@ -1168,7 +1193,7 @@
         throw new SolrException(ErrorCode.BAD_REQUEST, "Collection '" + collectionName + "' does not exist, no action taken.");
       }
 
-      Map<String, Object> params = copy(req.getParams(), null, COLLECTION_PROP, CoreAdminParams.COMMIT_NAME);
+      Map<String, Object> params = copy(req.getParams(), null, COLLECTION_PROP, FOLLOW_ALIASES, CoreAdminParams.COMMIT_NAME);
       return params;
     }),
     LISTSNAPSHOTS_OP(LISTSNAPSHOTS, (req, rsp, h) -> {
@@ -1211,7 +1236,8 @@
           WAIT_FOR_FINAL_STATE,
           IN_PLACE_MOVE,
           "replica",
-          "shard");
+          "shard",
+          FOLLOW_ALIASES);
     }),
     DELETENODE_OP(DELETENODE, (req, rsp, h) -> copy(req.getParams().required(), null, "node"));
 
diff --git a/solr/core/src/test/org/apache/solr/cloud/CollectionsAPISolrJTest.java b/solr/core/src/test/org/apache/solr/cloud/CollectionsAPISolrJTest.java
index ab73781..34d1b8d 100644
--- a/solr/core/src/test/org/apache/solr/cloud/CollectionsAPISolrJTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/CollectionsAPISolrJTest.java
@@ -53,9 +53,11 @@
 import org.apache.solr.client.solrj.response.CoreAdminResponse;
 import org.apache.solr.client.solrj.response.QueryResponse;
 import org.apache.solr.client.solrj.response.V2Response;
+import org.apache.solr.common.SolrDocument;
 import org.apache.solr.common.SolrInputDocument;
 import org.apache.solr.common.cloud.Aliases;
 import org.apache.solr.common.cloud.ClusterProperties;
+import org.apache.solr.common.cloud.ClusterState;
 import org.apache.solr.common.cloud.DocCollection;
 import org.apache.solr.common.cloud.Replica;
 import org.apache.solr.common.cloud.Slice;
@@ -794,8 +796,20 @@
 
   @Test
   public void testRenameCollection() throws Exception {
-    String collectionName1 = "testRename_collection1";
-    String collectionName2 = "testRename_collection2";
+    doTestRenameCollection(true);
+    CollectionAdminRequest.deleteAlias("col1").process(cluster.getSolrClient());
+    CollectionAdminRequest.deleteAlias("col2").process(cluster.getSolrClient());
+    CollectionAdminRequest.deleteAlias("foo").process(cluster.getSolrClient());
+    CollectionAdminRequest.deleteAlias("simpleAlias").process(cluster.getSolrClient());
+    CollectionAdminRequest.deleteAlias("catAlias").process(cluster.getSolrClient());
+    CollectionAdminRequest.deleteAlias("compoundAlias").process(cluster.getSolrClient());
+    cluster.getSolrClient().getZkStateReader().aliasesManager.update();
+    doTestRenameCollection(false);
+  }
+
+  private void doTestRenameCollection(boolean followAliases) throws Exception {
+    String collectionName1 = "testRename1_" + followAliases;
+    String collectionName2 = "testRename2_" + followAliases;
     CollectionAdminRequest.createCollection(collectionName1, "conf", 1, 1).setAlias("col1").process(cluster.getSolrClient());
     CollectionAdminRequest.createCollection(collectionName2, "conf", 1, 1).setAlias("col2").process(cluster.getSolrClient());
 
@@ -810,46 +824,160 @@
     CollectionAdminRequest.createCategoryRoutedAlias("catAlias", "field1", 100,
         CollectionAdminRequest.createCollection("_unused_", "conf", 1, 1)).process(cluster.getSolrClient());
 
-    CollectionAdminRequest.renameCollection("col1", "foo").process(cluster.getSolrClient());
+    CollectionAdminRequest.Rename rename = CollectionAdminRequest.renameCollection("col1", "foo");
+    rename.setFollowAliases(followAliases);
     ZkStateReader zkStateReader = cluster.getSolrClient().getZkStateReader();
-    zkStateReader.aliasesManager.update();
+    Aliases aliases;
+    if (!followAliases) {
+      try {
+        rename.process(cluster.getSolrClient());
+      } catch (Exception e) {
+        assertTrue(e.toString(), e.toString().contains("source collection 'col1' not found"));
+      }
+    } else {
+      rename.process(cluster.getSolrClient());
+      zkStateReader.aliasesManager.update();
 
-    Aliases aliases = zkStateReader.getAliases();
-    assertEquals(aliases.getCollectionAliasListMap().toString(), collectionName1, aliases.resolveSimpleAlias("foo"));
-    assertEquals(aliases.getCollectionAliasListMap().toString(), collectionName1, aliases.resolveSimpleAlias("simpleAlias"));
-    List<String> compoundAliases = aliases.resolveAliases("compoundAlias");
-    assertEquals(compoundAliases.toString(), 2, compoundAliases.size());
-    assertTrue(compoundAliases.toString(), compoundAliases.contains(collectionName1));
-    assertTrue(compoundAliases.toString(), compoundAliases.contains(collectionName2));
+      aliases = zkStateReader.getAliases();
+      assertEquals(aliases.getCollectionAliasListMap().toString(), collectionName1, aliases.resolveSimpleAlias("foo"));
+      assertEquals(aliases.getCollectionAliasListMap().toString(), collectionName1, aliases.resolveSimpleAlias("simpleAlias"));
+      List<String> compoundAliases = aliases.resolveAliases("compoundAlias");
+      assertEquals(compoundAliases.toString(), 2, compoundAliases.size());
+      assertTrue(compoundAliases.toString(), compoundAliases.contains(collectionName1));
+      assertTrue(compoundAliases.toString(), compoundAliases.contains(collectionName2));
+    }
 
     CollectionAdminRequest.renameCollection(collectionName1, collectionName2).process(cluster.getSolrClient());
     zkStateReader.aliasesManager.update();
 
     aliases = zkStateReader.getAliases();
-    assertEquals(aliases.getCollectionAliasListMap().toString(), collectionName2, aliases.resolveSimpleAlias("foo"));
+    if (followAliases) {
+      assertEquals(aliases.getCollectionAliasListMap().toString(), collectionName2, aliases.resolveSimpleAlias("foo"));
+    }
     assertEquals(aliases.getCollectionAliasListMap().toString(), collectionName2, aliases.resolveSimpleAlias("simpleAlias"));
     assertEquals(aliases.getCollectionAliasListMap().toString(), collectionName2, aliases.resolveSimpleAlias(collectionName1));
     // we renamed col1 -> col2 so the compound alias contains only "col2,col2" which is reduced to col2
-    compoundAliases = aliases.resolveAliases("compoundAlias");
+    List<String> compoundAliases = aliases.resolveAliases("compoundAlias");
     assertEquals(compoundAliases.toString(), 1, compoundAliases.size());
     assertTrue(compoundAliases.toString(), compoundAliases.contains(collectionName2));
 
     try {
-      CollectionAdminRequest.renameCollection("catAlias", "bar").process(cluster.getSolrClient());
+      rename = CollectionAdminRequest.renameCollection("catAlias", "bar");
+      rename.setFollowAliases(followAliases);
+      rename.process(cluster.getSolrClient());
       fail("category-based alias renaming should fail");
     } catch (Exception e) {
-      assertTrue(e.toString().contains("is a routed alias"));
+      if (followAliases) {
+        assertTrue(e.toString(), e.toString().contains("is a routed alias"));
+      } else {
+        assertTrue(e.toString(), e.toString().contains("source collection 'catAlias' not found"));
+      }
     }
 
     try {
-      CollectionAdminRequest.renameCollection("col2", "foo").process(cluster.getSolrClient());
-      fail("shuold fail because 'foo' already exists");
+      rename = CollectionAdminRequest.renameCollection("col2", "foo");
+      rename.setFollowAliases(followAliases);
+      rename.process(cluster.getSolrClient());
+      fail("should fail because 'foo' already exists");
     } catch (Exception e) {
-      assertTrue(e.toString().contains("exists"));
+      if (followAliases) {
+        assertTrue(e.toString(), e.toString().contains("exists"));
+      } else {
+        assertTrue(e.toString(), e.toString().contains("source collection 'col2' not found"));
+      }
     }
   }
 
   @Test
+  public void testDeleteAliasedCollection() throws Exception {
+    CloudSolrClient solrClient = cluster.getSolrClient();
+    String collectionName1 = "aliasedCollection1";
+    String collectionName2 = "aliasedCollection2";
+    CollectionAdminRequest.createCollection(collectionName1, "conf", 1, 1).process(solrClient);
+    CollectionAdminRequest.createCollection(collectionName2, "conf", 1, 1).process(solrClient);
+
+    cluster.waitForActiveCollection(collectionName1, 1, 1);
+    cluster.waitForActiveCollection(collectionName2, 1, 1);
+
+    waitForState("Expected collection1 to be created with 1 shard and 1 replica", collectionName1, clusterShape(1, 1));
+    waitForState("Expected collection2 to be created with 1 shard and 1 replica", collectionName2, clusterShape(1, 1));
+
+    SolrInputDocument doc = new SolrInputDocument("id", "1");
+    solrClient.add(collectionName1, doc);
+    doc = new SolrInputDocument("id", "2");
+    solrClient.add(collectionName2, doc);
+    solrClient.commit(collectionName1);
+    solrClient.commit(collectionName2);
+
+    assertDoc(solrClient, collectionName1, "1");
+    assertDoc(solrClient, collectionName2, "2");
+
+    CollectionAdminRequest.createAlias(collectionName1, collectionName2).process(solrClient);
+
+    RetryUtil.retryUntil("didn't get the new aliases", 10, 1000, TimeUnit.MILLISECONDS, () -> {
+      try {
+        solrClient.getZkStateReader().aliasesManager.update();
+        return solrClient.getZkStateReader().getAliases()
+            .resolveSimpleAlias(collectionName1).equals(collectionName2);
+      } catch (Exception e) {
+        fail("exception caught refreshing aliases: " + e);
+        return false;
+      }
+    });
+
+    // both results should come from collection 2
+    assertDoc(solrClient, collectionName1, "2"); // aliased
+    assertDoc(solrClient, collectionName2, "2"); // direct
+
+    // should be able to remove collection 1 when followAliases = false
+    CollectionAdminRequest.Delete delete = CollectionAdminRequest.deleteCollection(collectionName1);
+    delete.setFollowAliases(false);
+    delete.process(solrClient);
+    ClusterState state = solrClient.getClusterStateProvider().getClusterState();
+    assertFalse(state.getCollectionsMap().toString(), state.hasCollection(collectionName1));
+    // search should still work, returning results from collection 2
+    assertDoc(solrClient, collectionName1, "2"); // aliased
+    assertDoc(solrClient, collectionName2, "2"); // direct
+
+    // without aliases this collection doesn't exist anymore
+    delete = CollectionAdminRequest.deleteCollection(collectionName1);
+    delete.setFollowAliases(false);
+    try {
+      delete.process(solrClient);
+      fail("delete of nonexistent collection 1 should have failed when followAliases=false");
+    } catch (Exception e) {
+      assertTrue(e.toString(), e.toString().contains(collectionName1));
+    }
+
+    // with followAliases=true collection 2 (and the alias) should both be removed
+    delete.setFollowAliases(true);
+    delete.process(solrClient);
+
+    state = solrClient.getClusterStateProvider().getClusterState();
+    // the collection is gone
+    assertFalse(state.getCollectionsMap().toString(), state.hasCollection(collectionName2));
+
+    // and the alias is gone
+    RetryUtil.retryUntil("didn't get the new aliases", 10, 1000, TimeUnit.MILLISECONDS, () -> {
+      try {
+        solrClient.getZkStateReader().aliasesManager.update();
+        return !solrClient.getZkStateReader().getAliases().hasAlias(collectionName1);
+      } catch (Exception e) {
+        fail("exception caught refreshing aliases: " + e);
+        return false;
+      }
+    });
+  }
+
+  private void assertDoc(CloudSolrClient solrClient, String collection, String id) throws Exception {
+    QueryResponse rsp = solrClient.query(collection, params(CommonParams.Q, "*:*"));
+    assertEquals(rsp.toString(), 1, rsp.getResults().getNumFound());
+    SolrDocument sdoc = rsp.getResults().get(0);
+    assertEquals(sdoc.toString(), id, sdoc.getFieldValue("id"));
+
+  }
+
+  @Test
   public void testOverseerStatus() throws IOException, SolrServerException {
     CollectionAdminResponse response = new CollectionAdminRequest.OverseerStatus().process(cluster.getSolrClient());
     assertEquals(0, response.getStatus());
diff --git a/solr/core/src/test/org/apache/solr/cloud/ReindexCollectionTest.java b/solr/core/src/test/org/apache/solr/cloud/ReindexCollectionTest.java
index 6c84253..d45bcb5 100644
--- a/solr/core/src/test/org/apache/solr/cloud/ReindexCollectionTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/ReindexCollectionTest.java
@@ -142,8 +142,16 @@
     assertEquals("copied num docs", NUM_DOCS, queryResponse.getResults().getNumFound());
   }
 
+  @Test
   public void testSameTargetReindexing() throws Exception {
-    final String sourceCollection = "sameTargetReindexing";
+    doTestSameTargetReindexing(false, false);
+    doTestSameTargetReindexing(false, true);
+    doTestSameTargetReindexing(true, false);
+    doTestSameTargetReindexing(true, true);
+  }
+
+  private void doTestSameTargetReindexing(boolean sourceRemove, boolean followAliases) throws Exception {
+    final String sourceCollection = "sameTargetReindexing_" + sourceRemove + "_" + followAliases;
     final String targetCollection = sourceCollection;
 
     createCollection(sourceCollection, "conf1", 2, 2);
@@ -152,6 +160,8 @@
 
     CollectionAdminRequest.ReindexCollection req = CollectionAdminRequest.reindexCollection(sourceCollection)
         .setTarget(targetCollection);
+    req.setRemoveSource(sourceRemove);
+    req.setFollowAliases(followAliases);
     req.process(solrClient);
 
     String realTargetCollection = null;
@@ -175,9 +185,16 @@
       ReindexCollectionCmd.State state = ReindexCollectionCmd.State.get(coll.getStr(ReindexCollectionCmd.REINDEXING_STATE));
       return ReindexCollectionCmd.State.FINISHED == state;
     });
+
+    solrClient.getZkStateReader().aliasesManager.update();
+
     // verify the target docs exist
     QueryResponse rsp = solrClient.query(targetCollection, params(CommonParams.Q, "*:*"));
     assertEquals("copied num docs", NUM_DOCS, rsp.getResults().getNumFound());
+    ClusterState state = solrClient.getClusterStateProvider().getClusterState();
+    if (sourceRemove) {
+      assertFalse("source collection still present", state.hasCollection(sourceCollection));
+    }
   }
 
   @Test
diff --git a/solr/solr-ref-guide/src/aliases.adoc b/solr/solr-ref-guide/src/aliases.adoc
index 4699cf4..80fabc6 100644
--- a/solr/solr-ref-guide/src/aliases.adoc
+++ b/solr/solr-ref-guide/src/aliases.adoc
@@ -282,9 +282,15 @@
 Starting with version 8.1 SolrCloud supports using alias names in collection commands where normally a
 collection name is expected. This works only when the following criteria are satisfied:
 
+* a request parameter `followAliases=true` is used
 * an alias must not refer to more than one collection
 * an alias must not refer to a <<Routed Aliases,Routed Alias>> (see below)
 
-If all criteria are satisfied then the command will resolve alias names and operate on the collections the aliases
+If all criteria are satisfied then the command will resolve all alias names and operate on the collections the aliases
 refer to as if it was invoked with the collection names instead. Otherwise the command will not be executed and
 an exception will be thrown.
+
+The `followAliases=true` parameter should be used with care so that the resolved targets are indeed the intended ones.
+In case of multi-level aliases or shadow aliases (an alias with the same name as an existing collection but pointing
+to other collections) the use of this option is strongly discouraged because effects may be difficult to
+predict correctly.
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/request/CollectionAdminRequest.java b/solr/solrj/src/java/org/apache/solr/client/solrj/request/CollectionAdminRequest.java
index b747f7e..8729bfd 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/request/CollectionAdminRequest.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/request/CollectionAdminRequest.java
@@ -246,6 +246,7 @@
   protected abstract static class AsyncCollectionSpecificAdminRequest extends AsyncCollectionAdminRequest {
 
     protected String collection;
+    protected Boolean followAliases;
 
     public AsyncCollectionSpecificAdminRequest(CollectionAction action, String collection) {
       super(action);
@@ -256,10 +257,15 @@
       return collection;
     }
 
+    public void setFollowAliases(Boolean followAliases) {
+      this.followAliases = followAliases;
+    }
+
     @Override
     public SolrParams getParams() {
       ModifiableSolrParams params = new ModifiableSolrParams(super.getParams());
       params.set(CoreAdminParams.NAME, collection);
+      params.setNonNull(CollectionAdminParams.FOLLOW_ALIASES, followAliases);
       return params;
     }
   }
diff --git a/solr/solrj/src/java/org/apache/solr/common/params/CollectionAdminParams.java b/solr/solrj/src/java/org/apache/solr/common/params/CollectionAdminParams.java
index 5291b7d..5af2345 100644
--- a/solr/solrj/src/java/org/apache/solr/common/params/CollectionAdminParams.java
+++ b/solr/solrj/src/java/org/apache/solr/common/params/CollectionAdminParams.java
@@ -124,4 +124,7 @@
    * Prefix for {@link org.apache.solr.common.cloud.DocRouter} properties
    */
   String ROUTER_PREFIX = "router.";
+
+  /** Option to follow aliases when deciding the target of a collection admin command. */
+  String FOLLOW_ALIASES = "followAliases";
 }