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

morningman pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/master by this push:
     new 4b0e660b82f [refactor](catalog) drop hadoop source imports from 
fe-core and fe-common (#66324)
4b0e660b82f is described below

commit 4b0e660b82fb3321fc966faf8a8d4e735ed0d3b5
Author: Mingyu Chen (Rayner) <[email protected]>
AuthorDate: Sat Aug 1 14:48:45 2026 +0800

    [refactor](catalog) drop hadoop source imports from fe-core and fe-common 
(#66324)
    
    ### What problem does this PR solve?
    
    Issue Number: #65185
    
    Related PR: #66004
    
    Problem Summary:
    
    Now that catalogs go through the connector/filesystem SPIs, fe-core and
    fe-common no
    longer have a reason to compile against hadoop, but a handful of source
    imports were
    still left over from the pre-SPI code. This removes them.
    
    Scope is deliberately narrow: **source imports only**. The hadoop pom
    dependencies stay
    exactly as they are, because they remain reachable at runtime —
    `ranger-plugins-common`
    inherits from `org.apache.hadoop.conf.Configuration`, `hive-exec`
    supplies the UDF base
    class that `CREATE FUNCTION` resolves, and `fe-kerberos` uses
    `UserGroupInformation`.
    Dropping the jars is a separate, larger question and is not attempted
    here.
    
    After this PR, `grep -rn "^import org.apache.hadoop" fe/fe-core/src
    fe/fe-common/src`
    returns exactly one line: `RangerHiveAuditHandler`, which is a
    sanctioned exception. The
    type is imposed by Ranger's own API, and `ranger-plugins-common` puts
    hadoop on the
    classpath regardless, so removing that import would remove no
    dependency. It is
    documented as such in the code rather than worked around.
    
    The six commits are independent steps and are easiest to review one at a
    time:
    
    | Commit | What moves |
    |---|---|
    | `4314ad2` | Dead and trivial imports: delete `CatalogConfigFileUtils`
    (no caller; `fe-filesystem-hdfs*` already carries the port) and
    `LocationPath.getTempWritePath` (no caller); inline two hadoop constants
    that were plain strings; document the Ranger exception |
    | `29649e2` | `hadoop-huaweicloud` moves from fe-core to the
    `fe-filesystem-obs` plugin, together with the `huawei-obs-sdk`
    repository declaration. The only FE reference is the `Class.forName`
    probe in `ObsFileSystemProperties`, which must resolve against that
    plugin's classloader to report the truth |
    | `90339cd` | `FileSplitter` takes a new fe-core `FileBlockLocation`
    instead of hadoop's `BlockLocation`. Only
    `getOffset`/`getLength`/`getHosts` were ever read |
    | `c55800a` | `LocationPath.toStorageLocation()` returns the existing
    `org.apache.doris.filesystem.Location` instead of a hadoop `Path`;
    `getPath()` is deleted in favour of the `fsIdentifier` the class already
    computes. **See the behaviour-change note below** |
    | `592e8aa` | The Azure OAuth2 backend map moves from `StorageAdapter`
    into `fe-filesystem-azure`, which is where the legacy `AzureProperties`
    owned it. This deletes ~120 lines of fe-core code that turned out to be
    unreachable |
    | `464efa4` | `StageUtil`'s `GlobExpander`/`GlobFilter` use is ported to
    a package-private `GlobPatterns`, compiled with re2j — the same engine
    hadoop's `GlobPattern` uses, and already a declared fe-core dependency |
    
    Two notes that are easy to miss on review:
    
    - `hadoop.fs.GlobFilter` reads like a wildcard predicate but its
    constructor is also a
    validator: it rejects `a[b`, `a{b`, a trailing backslash and `[z-a]`,
    and `analyzeGlob`
    surfaces that as a `DdlException`. Porting only the predicate would have
    accepted those
    globs and quietly listed an unintended object-store prefix instead of
    failing the
      statement, so the validation is ported with it.
    - `StorageAdapter.getHadoopStorageConfig()` had no caller anywhere in
    the tree; the only
    consumer was the adapter's own Azure OAuth2 arm. That map is still
    load-bearing (BE
    routes Microsoft Fabric OneLake locations to `FILE_HDFS`, and
    `hdfs_builder` feeds every
    entry into its JNI hadoop builder), so it is preserved key-for-key
    rather than dropped —
      it just lives in the Azure plugin now.
---
 .../doris/common/CatalogConfigFileUtils.java       |  85 ------
 fe/fe-core/pom.xml                                 |  26 +-
 .../org/apache/doris/catalog/HdfsStorageVault.java |   6 +-
 .../ranger/hive/RangerHiveAuditHandler.java        |  15 ++
 .../org/apache/doris/cloud/stage/GlobPatterns.java | 298 +++++++++++++++++++++
 .../org/apache/doris/cloud/stage/StageUtil.java    |  14 +-
 .../org/apache/doris/common/util/LocationPath.java |  27 +-
 .../doris/datasource/scan/FileGroupInfo.java       |   8 +-
 .../doris/datasource/scan/FileQueryScanNode.java   |   8 +-
 .../doris/datasource/split/FileBlockLocation.java  |  58 ++++
 .../doris/datasource/split/FileSplitter.java       |  13 +-
 .../doris/datasource/storage/StorageAdapter.java   | 229 +---------------
 .../doris/nereids/load/NereidsFileGroupInfo.java   |   8 +-
 .../apache/doris/cloud/stage/StageGlobTest.java    | 164 ++++++++++++
 .../doris/datasource/split/FileSplitterTest.java   |  62 ++++-
 .../doris/planner/FederationBackendPolicyTest.java |   5 +-
 fe/fe-filesystem/fe-filesystem-azure/pom.xml       |  37 +++
 .../azure/AzureFileSystemProperties.java           |  49 +++-
 .../azure/AzureFileSystemPropertiesTest.java       |  72 +++++
 fe/fe-filesystem/fe-filesystem-obs/pom.xml         |  43 +++
 20 files changed, 844 insertions(+), 383 deletions(-)

diff --git 
a/fe/fe-common/src/main/java/org/apache/doris/common/CatalogConfigFileUtils.java
 
b/fe/fe-common/src/main/java/org/apache/doris/common/CatalogConfigFileUtils.java
deleted file mode 100644
index d8e9f2e5133..00000000000
--- 
a/fe/fe-common/src/main/java/org/apache/doris/common/CatalogConfigFileUtils.java
+++ /dev/null
@@ -1,85 +0,0 @@
-// Licensed to the Apache Software Foundation (ASF) under one
-// or more contributor license agreements.  See the NOTICE file
-// distributed with this work for additional information
-// regarding copyright ownership.  The ASF licenses this file
-// to you under the Apache License, Version 2.0 (the
-// "License"); you may not use this file except in compliance
-// with the License.  You may obtain a copy of the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing,
-// software distributed under the License is distributed on an
-// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-// KIND, either express or implied.  See the License for the
-// specific language governing permissions and limitations
-// under the License.
-
-package org.apache.doris.common;
-
-import org.apache.commons.lang3.StringUtils;
-import org.apache.hadoop.conf.Configuration;
-import org.apache.hadoop.fs.Path;
-
-import java.io.File;
-import java.util.function.BiConsumer;
-import java.util.function.Supplier;
-
-public class CatalogConfigFileUtils {
-
-    /**
-     * Generic method to load Hadoop-style configuration files from a 
directory.
-     *
-     * @param resourcesPath     Comma-separated list of resource file names to 
be loaded.
-     * @param configDir         Directory prefix where the configuration files 
reside.
-     * @param configSupplier    Supplier that creates a new configuration 
object.
-     * @param addResourceMethod Method to add a resource file to the 
configuration object.
-     * @param <T>               Type of the configuration.
-     * @return A configuration object loaded with the given resource files.
-     * @throws IllegalArgumentException if the resourcesPath is empty or if 
any file does not exist.
-     */
-    private static <T> T loadConfigFromDir(String resourcesPath, String 
configDir,
-                                           Supplier<T> configSupplier,
-                                           BiConsumer<T, Path> 
addResourceMethod) {
-        // Check if the provided resourcesPath is blank and throw an exception 
if so.
-        if (StringUtils.isBlank(resourcesPath)) {
-            throw new IllegalArgumentException("Config resource path is 
empty");
-        }
-
-        // Create a new configuration object.
-        T conf = configSupplier.get();
-
-        // Iterate over the comma-separated list of resource files.
-        for (String resource : resourcesPath.split(",")) {
-            // Construct the full path to the resource file.
-            String resourcePath = configDir + resource.trim();
-            File file = new File(resourcePath);
-
-            // Check if the file exists and is a regular file; if not, throw 
an exception.
-            if (file.exists() && file.isFile()) {
-                // Add the resource file to the configuration object.
-                addResourceMethod.accept(conf, new Path(file.toURI()));
-            } else {
-                // Throw an exception if the file does not exist or is not a 
regular file.
-                throw new IllegalArgumentException("Config resource file does 
not exist: " + resourcePath);
-            }
-        }
-        return conf;
-    }
-
-    /**
-     * Loads a Hadoop Configuration object from a list of files under the 
specified config directory.
-     *
-     * @param resourcesPath Comma-separated list of file names to be loaded.
-     * @return A Hadoop Configuration object.
-     * @throws IllegalArgumentException if the input is invalid or files are 
missing.
-     */
-    public static Configuration loadConfigurationFromHadoopConfDir(String 
resourcesPath) {
-        return loadConfigFromDir(
-                resourcesPath,
-                Config.hadoop_config_dir,
-                Configuration::new,
-                Configuration::addResource
-        );
-    }
-}
diff --git a/fe/fe-core/pom.xml b/fe/fe-core/pom.xml
index f6a2d1fc462..6744db2f762 100644
--- a/fe/fe-core/pom.xml
+++ b/fe/fe-core/pom.xml
@@ -391,11 +391,13 @@ under the License.
             <groupId>org.apache.commons</groupId>
             <artifactId>commons-lang3</artifactId>
         </dependency>
-        <!-- Runtime-only: hadoop-huaweicloud's OBSFileSystem 
(reflection-loaded, so no compile-time
-             reference reveals this) calls org.apache.commons.lang.StringUtils 
while declaring no
-             commons-lang of its own. This declaration is its only source on 
the FE classpath;
-             dropping it compiles green and then NoClassDefFoundErrors on OBS 
access.
-             scope=runtime keeps it off the compile classpath, so fe source 
stays commons-lang(2.x)-free. -->
+        <!-- Runtime-only, and still LOAD-BEARING after hadoop-huaweicloud 
moved out of this module:
+             OBSFileSystem calls org.apache.commons.lang.StringUtils while 
declaring no commons-lang of its
+             own, and the plugins that now carry hadoop-huaweicloud 
(fe-filesystem-obs, fe-connector-paimon)
+             do not declare commons-lang either — their child-first loaders 
miss it and fall through to
+             fe/lib, i.e. to this declaration. Dropping it compiles green and 
then NoClassDefFoundErrors on
+             OBS access. scope=runtime keeps it off the compile classpath, so 
fe source stays
+             commons-lang(2.x)-free. -->
         <dependency>
             <groupId>commons-lang</groupId>
             <artifactId>commons-lang</artifactId>
@@ -555,13 +557,6 @@ under the License.
             <groupId>com.amazonaws</groupId>
             <artifactId>aws-java-sdk-s3</artifactId>
         </dependency>
-        <!-- hadoop-huaweicloud provides OBSFileSystem; loaded via reflection 
in OBSProperties
-             with graceful S3A fallback — runtime only, not needed at compile 
time -->
-        <dependency>
-            <groupId>com.huaweicloud</groupId>
-            <artifactId>hadoop-huaweicloud</artifactId>
-            <scope>runtime</scope>
-        </dependency>
         <!-- antl4 The version of antlr-runtime in trino parser is need to be 
consistent with doris,
             when upgrade doris antlr-runtime version, should take care of 
trino-parser.-->
         <dependency>
@@ -864,13 +859,6 @@ under the License.
             <artifactId>fastutil-core</artifactId>
         </dependency>
     </dependencies>
-    <repositories>
-        <!-- for huawei obs sdk -->
-        <repository>
-            <id>huawei-obs-sdk</id>
-            
<url>https://repo.huaweicloud.com/repository/maven/huaweicloudsdk/</url>
-        </repository>
-    </repositories>
     <build>
         <finalName>doris-fe</finalName>
         <directory>${project.basedir}/target/</directory>
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/catalog/HdfsStorageVault.java 
b/fe/fe-core/src/main/java/org/apache/doris/catalog/HdfsStorageVault.java
index 5e2017092c8..c5851405958 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/catalog/HdfsStorageVault.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/HdfsStorageVault.java
@@ -32,7 +32,6 @@ import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Maps;
 import com.google.gson.annotations.SerializedName;
-import org.apache.hadoop.fs.CommonConfigurationKeysPublic;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
@@ -70,8 +69,9 @@ public class HdfsStorageVault extends StorageVault {
         public static String HADOOP_FS_NAME = "fs.defaultFS";
         public static String VAULT_PATH_PREFIX = "path_prefix";
         public static String HADOOP_USER_NAME = 
AuthenticationConfig.HADOOP_USER_NAME;
-        public static String HADOOP_SECURITY_AUTHENTICATION =
-                CommonConfigurationKeysPublic.HADOOP_SECURITY_AUTHENTICATION;
+        // Literal of hadoop's 
CommonConfigurationKeysPublic.HADOOP_SECURITY_AUTHENTICATION, inlined
+        // so fe-core carries no org.apache.hadoop import for a plain 
configuration-key string.
+        public static String HADOOP_SECURITY_AUTHENTICATION = 
"hadoop.security.authentication";
         public static String HADOOP_KERBEROS_KEYTAB = 
AuthenticationConfig.HADOOP_KERBEROS_KEYTAB;
         public static String HADOOP_KERBEROS_PRINCIPAL = 
AuthenticationConfig.HADOOP_KERBEROS_PRINCIPAL;
     }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/catalog/authorizer/ranger/hive/RangerHiveAuditHandler.java
 
b/fe/fe-core/src/main/java/org/apache/doris/catalog/authorizer/ranger/hive/RangerHiveAuditHandler.java
index 4f5d3678531..73d73a3933b 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/catalog/authorizer/ranger/hive/RangerHiveAuditHandler.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/catalog/authorizer/ranger/hive/RangerHiveAuditHandler.java
@@ -38,6 +38,21 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
+/**
+ * ATTN: the {@code org.apache.hadoop.conf.Configuration} import below is a 
SANCTIONED EXCEPTION to the
+ * fe-core hadoop-decoupling effort — do not "clean" it away.
+ *
+ * <p>The type is imposed by the Ranger library, not chosen by Doris: {@link 
RangerDefaultAuditHandler}
+ * declares the constructor as {@code 
RangerDefaultAuditHandler(org.apache.hadoop.conf.Configuration)}, and
+ * the only argument ever passed is {@code RangerBasePlugin.getConfig()}, 
whose type chain is
+ * {@code RangerPluginConfig -> RangerConfiguration -> 
org.apache.hadoop.conf.Configuration}. Narrowing the
+ * parameter to {@code RangerPluginConfig} would make a grep look clean while 
removing no dependency at all:
+ * ranger-plugins-common itself pulls hadoop-client-api/hadoop-client-runtime 
onto the fe-core classpath.
+ *
+ * <p>Removing hadoop from here therefore requires moving the whole Ranger 
authorizer out of fe-core, which
+ * contradicts the standing decision that this package is a generic 
external-catalog authorizer kept in
+ * fe-core unchanged.
+ */
 public class RangerHiveAuditHandler extends RangerDefaultAuditHandler {
 
     public static final String ACCESS_TYPE_ROWFILTER = "ROW_FILTER";
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/cloud/stage/GlobPatterns.java 
b/fe/fe-core/src/main/java/org/apache/doris/cloud/stage/GlobPatterns.java
new file mode 100644
index 00000000000..680c1126f72
--- /dev/null
+++ b/fe/fe-core/src/main/java/org/apache/doris/cloud/stage/GlobPatterns.java
@@ -0,0 +1,298 @@
+// 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.doris.cloud.stage;
+
+import com.google.re2j.Pattern;
+import com.google.re2j.PatternSyntaxException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Brace expansion and wildcard detection for the globs accepted by {@code 
COPY INTO}.
+ *
+ * <p>Ported from hadoop's {@code org.apache.hadoop.fs.GlobExpander} and
+ * {@code org.apache.hadoop.fs.GlobPattern} (both Apache-2.0), which {@link 
StageUtil} used
+ * directly until fe-core dropped its hadoop source imports. {@code StageUtil} 
already carries
+ * local copies of the neighbouring {@code org.apache.hadoop.fs.Globber} 
helpers, so these two
+ * complete the set.
+ *
+ * <p>Semantics are preserved exactly, down to compiling with re2j the way 
hadoop's
+ * {@code GlobPattern} does. That matters twice over:
+ * <ul>
+ *   <li>{@link #hasWildcard} decides whether a path component is a literal 
prefix or the point
+ *       where wildcard matching starts, which is what {@code 
StageUtil.analyzeGlob} turns into
+ *       the object-store listing prefix. Widening or narrowing it lists the 
wrong keys.</li>
+ *   <li>Both entry points reject malformed globs, and {@code analyzeGlob} 
surfaces that as a
+ *       {@code DdlException}. A laxer validator would silently accept e.g. 
{@code a[b} and copy
+ *       from an unintended prefix instead of failing the statement.</li>
+ * </ul>
+ *
+ * <p>Only what {@code StageUtil} calls is ported: hadoop's glob 
<em>matching</em> is not, since
+ * the compiled pattern is used here purely to validate. Note this is a 
different glob dialect
+ * from {@code org.apache.doris.common.GlobRegexUtil}, which treats {@code {} 
} as literal
+ * characters and has no notion of brace groups.
+ */
+final class GlobPatterns {
+
+    private static final char BACKSLASH = '\\';
+
+    private GlobPatterns() {
+    }
+
+    /**
+     * Expands the glob into a set of patterns none of which has a slash 
inside a curly bracket
+     * pair, e.g. {@code p{a/b,c/d}s} becomes {@code [pa/bs, pc/ds]}. Brace 
groups without a slash
+     * are left alone for {@link #hasWildcard} to handle per component.
+     *
+     * @throws IOException if the glob ends with a dangling escape character
+     */
+    static List<String> expand(String filePattern) throws IOException {
+        List<String> fullyExpanded = new ArrayList<>();
+        List<StringWithOffset> toExpand = new ArrayList<>();
+        toExpand.add(new StringWithOffset(filePattern, 0));
+        while (!toExpand.isEmpty()) {
+            StringWithOffset path = toExpand.remove(0);
+            List<StringWithOffset> expanded = expandLeftmost(path);
+            if (expanded == null) {
+                fullyExpanded.add(path.string);
+            } else {
+                toExpand.addAll(0, expanded);
+            }
+        }
+        return fullyExpanded;
+    }
+
+    /**
+     * Whether the glob contains a wildcard, i.e. any of {@code * ? [ {}. 
Escaped characters and
+     * the closing {@code ] } } do not count, matching hadoop's {@code 
GlobPattern.hasWildcard()}.
+     *
+     * @throws IOException if the glob is malformed, mirroring the {@code 
GlobFilter} constructor
+     */
+    static boolean hasWildcard(String glob) throws IOException {
+        try {
+            return compile(glob);
+        } catch (PatternSyntaxException e) {
+            throw new IOException("Illegal file pattern: " + e.getMessage(), 
e);
+        }
+    }
+
+    /**
+     * Translates the glob to a regex exactly as hadoop's {@code 
GlobPattern.set} does and compiles
+     * it for its validation side effect, returning whether a wildcard was 
seen.
+     */
+    private static boolean compile(String glob) {
+        StringBuilder regex = new StringBuilder();
+        int setOpen = 0;
+        int curlyOpen = 0;
+        int len = glob.length();
+        boolean hasWildcard = false;
+
+        for (int i = 0; i < len; i++) {
+            char c = glob.charAt(i);
+
+            switch (c) {
+                case BACKSLASH:
+                    if (++i >= len) {
+                        error("Missing escaped character", glob, i);
+                    }
+                    regex.append(c).append(glob.charAt(i));
+                    continue;
+                case '.':
+                case '$':
+                case '(':
+                case ')':
+                case '|':
+                case '+':
+                    // escape regex special chars that are not glob special 
chars
+                    regex.append(BACKSLASH);
+                    break;
+                case '*':
+                    regex.append('.');
+                    hasWildcard = true;
+                    break;
+                case '?':
+                    regex.append('.');
+                    hasWildcard = true;
+                    continue;
+                case '{': // start of a group
+                    regex.append("(?:"); // non-capturing
+                    curlyOpen++;
+                    hasWildcard = true;
+                    continue;
+                case ',':
+                    regex.append(curlyOpen > 0 ? '|' : c);
+                    continue;
+                case '}':
+                    if (curlyOpen > 0) {
+                        // end of a group
+                        curlyOpen--;
+                        regex.append(")");
+                        continue;
+                    }
+                    break;
+                case '[':
+                    if (setOpen > 0) {
+                        error("Unclosed character class", glob, i);
+                    }
+                    setOpen++;
+                    hasWildcard = true;
+                    break;
+                case '^': // ^ inside [...] can be unescaped
+                    if (setOpen == 0) {
+                        regex.append(BACKSLASH);
+                    }
+                    break;
+                case '!': // [! needs to be translated to [^
+                    regex.append(setOpen > 0 && '[' == glob.charAt(i - 1) ? 
'^' : '!');
+                    continue;
+                case ']':
+                    // Many set errors like [][] could not be easily detected 
here,
+                    // as []], []-] and [-] are all valid POSIX glob and java 
regex.
+                    // We'll just let the regex compiler do the real work.
+                    setOpen = 0;
+                    break;
+                default:
+            }
+            regex.append(c);
+        }
+
+        if (setOpen > 0) {
+            error("Unclosed character class", glob, len);
+        }
+        if (curlyOpen > 0) {
+            error("Unclosed group", glob, len);
+        }
+        // Compiled for validation only: re2j rejects character-class errors 
that the scan above
+        // deliberately leaves to it (see the ']' case), and hadoop surfaced 
those the same way.
+        // DOTALL is not about matching here -- it never runs -- but re2j 
echoes the flags into the
+        // PatternSyntaxException text, which ends up verbatim in the 
user-facing DdlException.
+        Pattern.compile(regex.toString(), Pattern.DOTALL);
+        return hasWildcard;
+    }
+
+    /**
+     * Expands the leftmost outer curly bracket pair that contains a slash.
+     *
+     * @return the expansions, or null when there is no such pair
+     */
+    private static List<StringWithOffset> expandLeftmost(StringWithOffset 
filePatternWithOffset)
+            throws IOException {
+        String filePattern = filePatternWithOffset.string;
+        int leftmost = leftmostOuterCurlyContainingSlash(filePattern, 
filePatternWithOffset.offset);
+        if (leftmost == -1) {
+            return null;
+        }
+        int curlyOpen = 0;
+        StringBuilder prefix = new StringBuilder(filePattern.substring(0, 
leftmost));
+        StringBuilder suffix = new StringBuilder();
+        List<String> alts = new ArrayList<>();
+        StringBuilder alt = new StringBuilder();
+        StringBuilder cur = prefix;
+        for (int i = leftmost; i < filePattern.length(); i++) {
+            char c = filePattern.charAt(i);
+            if (cur == suffix) {
+                cur.append(c);
+            } else if (c == '\\') {
+                i++;
+                if (i >= filePattern.length()) {
+                    throw new IOException("Illegal file pattern: "
+                            + "An escaped character does not present for glob "
+                            + filePattern + " at " + i);
+                }
+                c = filePattern.charAt(i);
+                cur.append(c);
+            } else if (c == '{') {
+                if (curlyOpen++ == 0) {
+                    alt.setLength(0);
+                    cur = alt;
+                } else {
+                    cur.append(c);
+                }
+            } else if (c == '}' && curlyOpen > 0) {
+                if (--curlyOpen == 0) {
+                    alts.add(alt.toString());
+                    alt.setLength(0);
+                    cur = suffix;
+                } else {
+                    cur.append(c);
+                }
+            } else if (c == ',') {
+                if (curlyOpen == 1) {
+                    alts.add(alt.toString());
+                    alt.setLength(0);
+                } else {
+                    cur.append(c);
+                }
+            } else {
+                cur.append(c);
+            }
+        }
+        List<StringWithOffset> exp = new ArrayList<>();
+        for (String string : alts) {
+            exp.add(new StringWithOffset(prefix + string + suffix, 
prefix.length()));
+        }
+        return exp;
+    }
+
+    /**
+     * @return the index of the leftmost opening curly bracket containing a 
slash, or -1
+     */
+    private static int leftmostOuterCurlyContainingSlash(String filePattern, 
int offset) throws IOException {
+        int curlyOpen = 0;
+        int leftmost = -1;
+        boolean seenSlash = false;
+        for (int i = offset; i < filePattern.length(); i++) {
+            char c = filePattern.charAt(i);
+            if (c == '\\') {
+                i++;
+                if (i >= filePattern.length()) {
+                    throw new IOException("Illegal file pattern: "
+                            + "An escaped character does not present for glob "
+                            + filePattern + " at " + i);
+                }
+            } else if (c == '{') {
+                if (curlyOpen++ == 0) {
+                    leftmost = i;
+                }
+            } else if (c == '}' && curlyOpen > 0) {
+                if (--curlyOpen == 0 && leftmost != -1 && seenSlash) {
+                    return leftmost;
+                }
+            } else if (c == '/' && curlyOpen > 0) {
+                seenSlash = true;
+            }
+        }
+        return -1;
+    }
+
+    private static void error(String message, String pattern, int pos) {
+        throw new PatternSyntaxException(String.format("%s at pos %d", 
message, pos), pattern);
+    }
+
+    private static class StringWithOffset {
+        private final String string;
+        private final int offset;
+
+        StringWithOffset(String string, int offset) {
+            this.string = string;
+            this.offset = offset;
+        }
+    }
+}
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/cloud/stage/StageUtil.java 
b/fe/fe-core/src/main/java/org/apache/doris/cloud/stage/StageUtil.java
index 8d306d83e66..79225bbf7ac 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/cloud/stage/StageUtil.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/cloud/stage/StageUtil.java
@@ -45,8 +45,6 @@ import com.google.common.base.Preconditions;
 import com.google.common.collect.Lists;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.tuple.Triple;
-import org.apache.hadoop.fs.GlobExpander;
-import org.apache.hadoop.fs.GlobFilter;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
@@ -302,7 +300,7 @@ public class StageUtil {
             return globs;
         }
         try {
-            List<String> flattenedPatterns = GlobExpander.expand(glob);
+            List<String> flattenedPatterns = GlobPatterns.expand(glob);
             for (String flattenedPattern : flattenedPatterns) {
                 try {
                     globs.addAll(analyzeFlattenedPattern(flattenedPattern));
@@ -326,8 +324,7 @@ public class StageUtil {
         boolean sawWildcard = false;
         for (int componentIdx = 0; componentIdx < components.size(); 
componentIdx++) {
             String component = components.get(componentIdx);
-            GlobFilter globFilter = new GlobFilter(component);
-            if (globFilter.hasPattern()) {
+            if (GlobPatterns.hasWildcard(component)) {
                 if (componentIdx == components.size() - 1) {
                     List<Pair<String, Boolean>> pairs = 
analyzeLastComponent(component);
                     if (pairs != null) {
@@ -356,8 +353,7 @@ public class StageUtil {
             String sub = component.substring(1, component.length() - 1);
             List<String> splits = splitByComma(sub);
             for (String split : splits) {
-                GlobFilter globFilter = new GlobFilter(split);
-                if (globFilter.hasPattern()) {
+                if (GlobPatterns.hasWildcard(split)) {
                     results.add(Pair.of(getComponentPrefix(split), true));
                 } else {
                     results.add(Pair.of(unescapePathComponent(split), false));
@@ -412,7 +408,7 @@ public class StageUtil {
     }
 
     /*
-     * Glob process method are referenced from {@link 
org.apache.hadoop.fs.Globber}
+     * Glob process method are referenced from {@code 
org.apache.hadoop.fs.Globber}
      */
     private static String unescapePathComponent(String name) {
         return name.replaceAll("\\\\(.)", "$1");
@@ -420,7 +416,7 @@ public class StageUtil {
 
     private static List<String> getPathComponents(String path) {
         ArrayList<String> ret = new ArrayList<>();
-        for (String component : 
path.split(org.apache.hadoop.fs.Path.SEPARATOR)) {
+        for (String component : path.split("/")) {
             if (!component.isEmpty()) {
                 ret.add(component);
             }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/common/util/LocationPath.java 
b/fe/fe-core/src/main/java/org/apache/doris/common/util/LocationPath.java
index 85badd97265..f6da8e0d85e 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/common/util/LocationPath.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/common/util/LocationPath.java
@@ -23,12 +23,12 @@ import org.apache.doris.datasource.storage.StorageRegistry;
 import org.apache.doris.datasource.storage.StorageTypeId;
 import org.apache.doris.datasource.storage.StorageUriUtils;
 import org.apache.doris.filesystem.FileSystemType;
+import org.apache.doris.filesystem.Location;
 import org.apache.doris.foundation.property.StoragePropertiesException;
 import org.apache.doris.thrift.TFileType;
 
 import com.google.common.base.Strings;
 import org.apache.commons.lang3.StringUtils;
-import org.apache.hadoop.fs.Path;
 
 import java.io.UnsupportedEncodingException;
 import java.net.URI;
@@ -37,7 +37,6 @@ import java.nio.charset.StandardCharsets;
 import java.nio.file.InvalidPathException;
 import java.nio.file.Paths;
 import java.util.Map;
-import java.util.UUID;
 
 /**
  * LocationPath is a utility class for parsing, validating, and normalizing 
storage location URIs.
@@ -367,13 +366,6 @@ public class LocationPath {
         return locationPath.getTFileTypeForBE();
     }
 
-    public static String getTempWritePath(String loc, String prefix) {
-        // If prefix is relative, it is resolved under loc; if absolute, it is 
used as the base path.
-        Path tempRoot = new Path(loc, prefix);
-        Path tempPath = new Path(tempRoot, 
UUID.randomUUID().toString().replace("-", ""));
-        return tempPath.toString();
-    }
-
     public TFileType getTFileTypeForBE() {
         if (("abfs".equals(schema) || "abfss".equals(schema))
                 && StorageUriUtils.isOneLakeLocation(normalizedLocation)) {
@@ -388,10 +380,19 @@ public class LocationPath {
     /**
      * The converted path is used for BE
      *
+     * <p>Returns the normalized location verbatim. This used to route through 
hadoop's
+     * {@code Path}, which rewrote the string on the way out: it collapsed 
repeated slashes,
+     * dropped a trailing slash, resolved {@code .}/{@code ..} segments, and 
turned an empty
+     * authority into a single slash ({@code hdfs:///a/b} became {@code 
hdfs:/a/b}). None of that
+     * is wanted here — object stores key on the exact byte string, so {@code 
a//b} and {@code a/b}
+     * name different objects, and collapsing them rewrites the key. This 
matches
+     * {@link Location}, which stores what it was given, and Trino's {@code 
io.trino.filesystem
+     * .Location}, whose javadoc likewise states it does not follow URI format 
rules.
+     *
      * @return BE scan range path
      */
-    public Path toStorageLocation() {
-        return new Path(normalizedLocation);
+    public Location toStorageLocation() {
+        return Location.of(normalizedLocation);
     }
 
 
@@ -420,8 +421,4 @@ public class LocationPath {
     public StorageAdapter getStorageAdapter() {
         return storageAdapter;
     }
-
-    public Path getPath() {
-        return new Path(normalizedLocation);
-    }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/datasource/scan/FileGroupInfo.java 
b/fe/fe-core/src/main/java/org/apache/doris/datasource/scan/FileGroupInfo.java
index 680989d959e..b1d70b467fc 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/datasource/scan/FileGroupInfo.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/datasource/scan/FileGroupInfo.java
@@ -26,6 +26,7 @@ import org.apache.doris.common.AnalysisException;
 import org.apache.doris.common.Config;
 import org.apache.doris.common.Pair;
 import org.apache.doris.common.UserException;
+import org.apache.doris.common.util.LocationPath;
 import org.apache.doris.common.util.Util;
 import org.apache.doris.load.BrokerFileGroup;
 import org.apache.doris.planner.FileLoadScanNode;
@@ -47,11 +48,9 @@ import org.apache.doris.thrift.TUniqueKeyUpdateMode;
 
 import com.google.common.base.Preconditions;
 import com.google.common.collect.Lists;
-import org.apache.hadoop.fs.Path;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
-import java.net.URI;
 import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.List;
@@ -413,8 +412,9 @@ public class FileGroupInfo {
             rangeDesc.setColumnsFromPathKeys(columnsFromPathKeys);
             rangeDesc.setColumnsFromPathIsNull(columnsFromPathIsNull);
             if (getFileType() == TFileType.FILE_HDFS) {
-                URI fileUri = new Path(fileStatus.path).toUri();
-                rangeDesc.setFsName(fileUri.getScheme() + "://" + 
fileUri.getAuthority());
+                // LocationPath parses scheme and authority into fsIdentifier 
in exactly this
+                // "scheme://authority" shape, without pulling in hadoop.
+                
rangeDesc.setFsName(LocationPath.of(fileStatus.path).getFsIdentifier());
             }
         } else {
             // for stream load
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/datasource/scan/FileQueryScanNode.java
 
b/fe/fe-core/src/main/java/org/apache/doris/datasource/scan/FileQueryScanNode.java
index 44a05404ed4..75bac73beaa 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/datasource/scan/FileQueryScanNode.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/datasource/scan/FileQueryScanNode.java
@@ -77,7 +77,6 @@ import lombok.Getter;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
-import java.net.URI;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
@@ -583,8 +582,11 @@ public abstract class FileQueryScanNode extends 
FileScanNode {
         rangeDesc.setFileType(fileSplit.getLocationType());
         rangeDesc.setPath(fileSplit.getPath().toStorageLocation().toString());
         if (fileSplit.getLocationType() == TFileType.FILE_HDFS) {
-            URI fileUri = fileSplit.getPath().getPath().toUri();
-            rangeDesc.setFsName(fileUri.getScheme() + "://" + 
fileUri.getAuthority());
+            // LocationPath already parsed scheme and authority into 
fsIdentifier, in exactly this
+            // "scheme://authority" shape; re-deriving it through hadoop's 
Path bought nothing. It also
+            // emitted the literal "hdfs://null" when a location carried no 
authority, where
+            // fsIdentifier yields "hdfs://".
+            rangeDesc.setFsName(fileSplit.getPath().getFsIdentifier());
         }
         rangeDesc.setModificationTime(fileSplit.getModificationTime());
         return rangeDesc;
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/datasource/split/FileBlockLocation.java
 
b/fe/fe-core/src/main/java/org/apache/doris/datasource/split/FileBlockLocation.java
new file mode 100644
index 00000000000..033f2f4961d
--- /dev/null
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/datasource/split/FileBlockLocation.java
@@ -0,0 +1,58 @@
+// 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.doris.datasource.split;
+
+/**
+ * One block of a file, described by its offset, its length and the hosts 
holding it.
+ *
+ * <p>This is the exact subset of {@code org.apache.hadoop.fs.BlockLocation} 
that
+ * {@link FileSplitter} reads — {@code getOffset()}, {@code getLength()} and 
{@code getHosts()} —
+ * reimplemented as a fe-core type so split planning carries no hadoop 
dependency. The hadoop
+ * constructor's unused {@code names} argument is not carried over.
+ *
+ * <p>Null {@code hosts} is normalized to an empty array, matching hadoop: 
{@code new
+ * BlockLocation(null, null, 0, len).getHosts()} returns {@code String[0]}, 
never null. Splitting a
+ * file with no block information relies on that — it builds a single 
synthetic block with null
+ * hosts, and the resulting splits must carry an empty host array rather than 
a null one.
+ */
+public class FileBlockLocation {
+
+    private static final String[] EMPTY_HOSTS = new String[0];
+
+    private final long offset;
+    private final long length;
+    private final String[] hosts;
+
+    public FileBlockLocation(String[] hosts, long offset, long length) {
+        this.hosts = hosts == null ? EMPTY_HOSTS : hosts;
+        this.offset = offset;
+        this.length = length;
+    }
+
+    public long getOffset() {
+        return offset;
+    }
+
+    public long getLength() {
+        return length;
+    }
+
+    public String[] getHosts() {
+        return hosts;
+    }
+}
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/datasource/split/FileSplitter.java 
b/fe/fe-core/src/main/java/org/apache/doris/datasource/split/FileSplitter.java
index 9ea50b95d5d..41bf5eaad6c 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/datasource/split/FileSplitter.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/datasource/split/FileSplitter.java
@@ -26,7 +26,6 @@ import com.google.common.base.Preconditions;
 import com.google.common.base.Verify;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
-import org.apache.hadoop.fs.BlockLocation;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
@@ -94,7 +93,7 @@ public class FileSplitter {
     public List<Split> splitFile(
                 LocationPath path,
                 long specifiedFileSplitSize,
-                BlockLocation[] blockLocations,
+                FileBlockLocation[] blockLocations,
                 long length,
                 long modificationTime,
                 boolean splittable,
@@ -108,8 +107,8 @@ public class FileSplitter {
         // Pass splitCreator.create() to set target file split size to 
calculate split weight.
         long targetFileSplitSize = specifiedFileSplitSize > 0 ? 
specifiedFileSplitSize : maxSplitSize;
         if (blockLocations == null) {
-            blockLocations = new BlockLocation[1];
-            blockLocations[0] = new BlockLocation(null, null, 0L, length);
+            blockLocations = new FileBlockLocation[1];
+            blockLocations[0] = new FileBlockLocation(null, 0L, length);
         }
         List<Split> result = Lists.newArrayList();
         TFileCompressType compressType = 
Util.inferFileCompressTypeByPath(path.getNormalizedLocation());
@@ -147,7 +146,7 @@ public class FileSplitter {
         // split file by block
         long start = 0;
         ImmutableList.Builder<InternalBlock> blockBuilder = 
ImmutableList.builder();
-        for (BlockLocation blockLocation : blockLocations) {
+        for (FileBlockLocation blockLocation : blockLocations) {
             // clamp the block range
             long blockStart = Math.max(start, blockLocation.getOffset());
             long blockEnd = Math.min(start + length, blockLocation.getOffset() 
+ blockLocation.getLength());
@@ -215,7 +214,7 @@ public class FileSplitter {
         }
     }
 
-    private int getBlockIndex(BlockLocation[] blkLocations, long offset) {
+    private int getBlockIndex(FileBlockLocation[] blkLocations, long offset) {
         if (blkLocations == null || blkLocations.length == 0) {
             return -1;
         }
@@ -225,7 +224,7 @@ public class FileSplitter {
                 return i;
             }
         }
-        BlockLocation last = blkLocations[blkLocations.length - 1];
+        FileBlockLocation last = blkLocations[blkLocations.length - 1];
         long fileLength = last.getOffset() + last.getLength() - 1L;
         throw new IllegalArgumentException(String.format("Offset %d is outside 
of file (0..%d)", offset, fileLength));
     }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/datasource/storage/StorageAdapter.java
 
b/fe/fe-core/src/main/java/org/apache/doris/datasource/storage/StorageAdapter.java
index 55583433a44..2c236218c8f 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/datasource/storage/StorageAdapter.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/datasource/storage/StorageAdapter.java
@@ -18,7 +18,6 @@
 package org.apache.doris.datasource.storage;
 
 import org.apache.doris.common.Config;
-import 
org.apache.doris.datasource.property.common.AwsCredentialsProviderFactory;
 import org.apache.doris.datasource.property.common.AwsCredentialsProviderMode;
 import org.apache.doris.filesystem.properties.FileSystemProperties;
 import org.apache.doris.filesystem.properties.HadoopStorageProperties;
@@ -29,17 +28,12 @@ import 
org.apache.doris.foundation.security.ExecutionAuthenticator;
 import org.apache.doris.fs.FileSystemPluginManager;
 
 import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import org.apache.commons.lang3.BooleanUtils;
 import org.apache.commons.lang3.StringUtils;
-import org.apache.hadoop.conf.Configuration;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
-import 
software.amazon.awssdk.auth.credentials.InstanceProfileCredentialsProvider;
 
 import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
@@ -52,8 +46,11 @@ import java.util.Set;
  *
  * <p>One adapter wraps one {@link FileSystemProperties} binding produced by
  * {@link FileSystemPluginManager#bindPrimary}/{@code bindAll}. Its public 
surface mirrors the
- * legacy typed storage-properties contract exactly — backend map,
- * Hadoop configuration, storage name, schemas, type — so consumers can 
migrate mechanically.
+ * legacy typed storage-properties contract exactly — backend map, storage 
name, schemas, type
+ * — so consumers can migrate mechanically. Building a hadoop {@code 
Configuration} is NOT part
+ * of that surface: the one legacy consumer (the Azure OAuth2 backend map) is 
now served by
+ * {@code AzureFileSystemProperties.toMap()} inside fe-filesystem-azure, which 
keeps fe-core
+ * source hadoop-free.
  * Every known SPI-vs-fe-core drift from the master plan's §2.4 parity ledger 
is reconciled here
  * (or in the SPI implementation, where noted); each reconciliation carries a
  * "align fe-core" comment referencing the ledger item.</p>
@@ -80,22 +77,6 @@ public final class StorageAdapter {
         pluginManager = manager;
     }
 
-    private static final String SIMPLE_AWS_CREDENTIALS_PROVIDER =
-            "org.apache.hadoop.fs.s3a.SimpleAWSCredentialsProvider";
-    private static final String ASSUMED_ROLE_CREDENTIAL_PROVIDER =
-            "org.apache.hadoop.fs.s3a.auth.AssumedRoleCredentialProvider";
-
-    /**
-     * Hadoop keys the facade re-derives for the S3 provider instead of taking 
the SPI values:
-     * fe-core gates them on accessKey-blank and on 
Config.aws_credentials_provider_version,
-     * which the SPI layer cannot see (align fe-core, ledger 2.4-3).
-     */
-    private static final Set<String> S3_CREDENTIAL_KEYS = ImmutableSet.of(
-            "fs.s3a.aws.credentials.provider",
-            "fs.s3a.assumed.role.arn",
-            "fs.s3a.assumed.role.credentials.provider",
-            "fs.s3a.assumed.role.external.id");
-
     /**
      * Legacy per-dialect alias prefix for {@code 
force_parsing_by_standard_uri} (fe-core
      * {@code AbstractS3CompatibleProperties} subclasses declared
@@ -123,14 +104,6 @@ public final class StorageAdapter {
     private final AwsCredentialsProviderMode s3CredentialsMode;
     /** Resolved once at construction (legacy bound it at init); read per file 
in listing loops. */
     private final String forceParsingByStandardUriValue;
-    /**
-     * Lazily built (legacy {@code X.of()} factories never built one; {@code 
new Configuration()}
-     * re-parses the Hadoop XML defaults and is too expensive for 
per-statement bindings that
-     * never read it, e.g. cloud COPY INTO). Volatile double-checked; 
construction is a pure
-     * function of final fields.
-     */
-    private volatile Configuration hadoopStorageConfig;
-    private volatile boolean hadoopStorageConfigBuilt;
     /**
      * Adapters are shared across threads (catalog adapter map, FS caches), so 
the lazily cached
      * map must be safely published — legacy classes were immune (eager init 
or fresh map per
@@ -313,25 +286,12 @@ public final class StorageAdapter {
         return spi.storageFamilyName();
     }
 
-    /** Legacy schemas() (drives ensureDisableCache): provider-declared legacy 
scheme set. */
+    /** Legacy schemas(): provider-declared legacy scheme set. */
     public Set<String> schemas() {
         // provider-declared legacy scheme set (ledger 2.4-6 note lives on 
legacyCacheSchemes)
         return spi.legacyCacheSchemes();
     }
 
-    /** Hadoop configuration equivalent of legacy getHadoopStorageConfig(); 
null for BROKER/HTTP. */
-    public Configuration getHadoopStorageConfig() {
-        if (!hadoopStorageConfigBuilt) {
-            synchronized (this) {
-                if (!hadoopStorageConfigBuilt) {
-                    hadoopStorageConfig = buildHadoopStorageConfig();
-                    hadoopStorageConfigBuilt = true;
-                }
-            }
-        }
-        return hadoopStorageConfig;
-    }
-
     /** Legacy isKerberos() — meaningful for the HDFS family only. */
     public boolean isKerberos() {
         return 
spi.toHadoopProperties().map(HadoopStorageProperties::isKerberos).orElse(false);
@@ -476,7 +436,13 @@ public final class StorageAdapter {
                 // Align fe-core: Broker/Local/Http return the raw user 
properties verbatim.
                 return origProps;
             case AZURE:
-                return azureBackendConfigProperties();
+                // Provider-owned, both auth types (the OAuth2 map is a hadoop 
Configuration dump
+                // built inside fe-filesystem-azure). Routed out here so it 
never reaches the
+                // S3-family alignment below, exactly as before.
+                return spi.toBackendProperties()
+                        .orElseThrow(() -> new IllegalStateException(
+                                "Provider " + providerKey + " exposes no 
backend properties"))
+                        .toMap();
             default:
                 break;
         }
@@ -493,20 +459,6 @@ public final class StorageAdapter {
         return base;
     }
 
-    /**
-     * Align fe-core, ledger 2.4-7: with OAuth2 the legacy AzureProperties 
dumps the ENTIRE
-     * Hadoop Configuration (hadoop defaults + fs.azure.* + user fs.* 
passthrough +
-     * disable-cache keys) as the backend map; shared-key uses the SPI's exact 
7-key map.
-     */
-    private Map<String, String> azureBackendConfigProperties() {
-        if (!isAzureOauth2()) {
-            return spi.toBackendProperties().orElseThrow().toMap();
-        }
-        Map<String, String> dump = new HashMap<>();
-        getHadoopStorageConfig().forEach(entry -> dump.put(entry.getKey(), 
entry.getValue()));
-        return dump;
-    }
-
     /**
      * Backend-map reconciliation for every S3-compatible provider 
(S3/OSS/OBS/COS/GCS/MinIO/Ozone).
      */
@@ -567,150 +519,6 @@ public final class StorageAdapter {
         return aligned;
     }
 
-    private Configuration buildHadoopStorageConfig() {
-        switch (type) {
-            case BROKER:
-            case HTTP:
-                // Align fe-core: BrokerProperties/HttpProperties leave 
hadoopStorageConfig null.
-                return null;
-            case LOCAL:
-                return buildLocalHadoopConfig();
-            default:
-                break;
-        }
-        // Align fe-core, ledger 2.4-2: `new Configuration()` loads 
core-default/core-site,
-        // where the SPI map is a bare key-value view.
-        Configuration conf = new Configuration();
-        Map<String, String> spiMap = spi.toHadoopProperties()
-                .orElseThrow(() -> new IllegalStateException(
-                        "Provider " + providerKey + " exposes no hadoop 
properties"))
-                .toHadoopConfigurationMap();
-        if ("JFS".equals(providerKey)) {
-            // fe-core builds the HDFS-family Configuration FROM the backend 
map, so the JFS
-            // auth-key alignment (see alignJfsBackendMap) must be 
materialized here too.
-            spiMap = alignJfsBackendMap(spiMap);
-        }
-        boolean isS3 = "S3".equals(providerKey);
-        boolean skipGcsAnonProvider = isGcsAnonymous();
-        for (Map.Entry<String, String> entry : spiMap.entrySet()) {
-            if (isS3 && S3_CREDENTIAL_KEYS.contains(entry.getKey())) {
-                // Re-derived below with fe-core Config gating (align fe-core, 
ledger 2.4-3).
-                continue;
-            }
-            if (skipGcsAnonProvider && 
"fs.s3a.aws.credentials.provider".equals(entry.getKey())) {
-                // Align fe-core: GCSProperties never sets an anonymous s3a 
credentials provider;
-                // the SPI's AnonymousAWSCredentialsProvider extra is dropped 
here.
-                continue;
-            }
-            conf.set(entry.getKey(), entry.getValue());
-        }
-        if (isS3) {
-            applyS3CredentialProviders(conf, 
(S3CompatibleFileSystemProperties) spi);
-        }
-        if ("AZURE".equals(providerKey) && !isAzureOauth2()) {
-            applyAzureAccountKeysFromConfig(conf);
-        }
-        appendUserFsConfig(conf);
-        ensureDisableCache(conf);
-        return conf;
-    }
-
-    private Configuration buildLocalHadoopConfig() {
-        // Align fe-core LocalProperties.initializeHadoopStorageConfig (Local 
has no SPI
-        // hadoop view; the two impl keys are fe-core knowledge).
-        Configuration conf = new Configuration();
-        conf.set("fs.local.impl", "org.apache.hadoop.fs.LocalFileSystem");
-        conf.set("fs.file.impl", "org.apache.hadoop.fs.LocalFileSystem");
-        appendUserFsConfig(conf);
-        ensureDisableCache(conf);
-        return conf;
-    }
-
-    /**
-     * Align fe-core, ledger 2.4-3: S3Properties only emits assumed-role keys 
when accessKey is
-     * blank, and selects the provider chain per 
Config.aws_credentials_provider_version — the
-     * SPI cannot see fe-core Config, so these four keys are owned by the 
facade.
-     */
-    private void applyS3CredentialProviders(Configuration conf, 
S3CompatibleFileSystemProperties s3) {
-        boolean v2 = 
Config.aws_credentials_provider_version.equalsIgnoreCase("v2");
-        if (StringUtils.isNotBlank(s3.getAccessKey())) {
-            // Static credentials win; access/secret/session keys came from 
the SPI map.
-            conf.set("fs.s3a.aws.credentials.provider", 
SIMPLE_AWS_CREDENTIALS_PROVIDER);
-            return;
-        }
-        if (StringUtils.isNotBlank(s3.getRoleArn())) {
-            conf.set("fs.s3a.assumed.role.arn", s3.getRoleArn());
-            conf.set("fs.s3a.aws.credentials.provider", 
ASSUMED_ROLE_CREDENTIAL_PROVIDER);
-            conf.set("fs.s3a.assumed.role.credentials.provider",
-                    v2 ? 
AwsCredentialsProviderFactory.getV2ClassName(s3CredentialsMode, false)
-                            : 
InstanceProfileCredentialsProvider.class.getName());
-            if (StringUtils.isNotBlank(s3.getExternalId())) {
-                conf.set("fs.s3a.assumed.role.external.id", 
s3.getExternalId());
-            }
-            return;
-        }
-        if (v2) {
-            conf.set("fs.s3a.aws.credentials.provider",
-                    
AwsCredentialsProviderFactory.getV2ClassName(s3CredentialsMode, true));
-        }
-        // v1 + anonymous: fe-core sets nothing and leaves the hadoop default 
untouched.
-    }
-
-    /**
-     * Align fe-core AzureProperties.setHDFSAzureAccountKeys: shared-key 
account keys are derived
-     * from Config.azure_blob_host_suffixes (blob + dfs endpoints, 
admin-extensible), not from the
-     * SPI's static suffix list. Runs before appendUserFsConfig so explicit 
user values still win.
-     */
-    private void applyAzureAccountKeysFromConfig(Configuration conf) {
-        Map<String, String> backend = 
spi.toBackendProperties().orElseThrow().toMap();
-        String accountName = backend.get("AWS_ACCESS_KEY");
-        String accountKey = backend.get("AWS_SECRET_KEY");
-        Set<String> suffixes = new LinkedHashSet<>();
-        if (Config.azure_blob_host_suffixes != null) {
-            for (String suffix : Config.azure_blob_host_suffixes) {
-                if (StringUtils.isBlank(suffix)) {
-                    continue;
-                }
-                String normalized = suffix.trim().toLowerCase(Locale.ROOT);
-                if (normalized.startsWith(".")) {
-                    normalized = normalized.substring(1);
-                }
-                if (!normalized.isEmpty()) {
-                    suffixes.add(normalized);
-                }
-            }
-        }
-        for (String suffix : suffixes) {
-            conf.set(String.format("fs.azure.account.key.%s.%s", accountName, 
suffix), accountKey);
-        }
-        conf.set("fs.azure.account.key", accountKey);
-    }
-
-    /** Ledger 2.4-8: user fs.* keys with non-blank values pass through into 
the configuration. */
-    private void appendUserFsConfig(Configuration conf) {
-        origProps.forEach((key, value) -> {
-            if (key.startsWith("fs.") && StringUtils.isNotBlank(value)) {
-                conf.set(key, value);
-            }
-        });
-    }
-
-    /**
-     * Ledger 2.4-8: per-schema FileSystem cache disabling over the LEGACY 
schemas() set, with an
-     * explicit user value taking precedence — copied from 
StorageProperties.ensureDisableCache.
-     */
-    private void ensureDisableCache(Configuration conf) {
-        for (String schema : schemas()) {
-            String key = "fs." + schema + ".impl.disable.cache";
-            String userValue = origProps.get(key);
-            if (StringUtils.isNotBlank(userValue)) {
-                conf.setBoolean(key, BooleanUtils.toBoolean(userValue));
-            } else {
-                conf.setBoolean(key, true);
-            }
-        }
-    }
-
     /** fe-core S3Properties alias list for s3.credentials_provider_type (no 
AWS_* alias). */
     private String feCoreS3CredentialsProviderType() {
         return firstNonBlank(origProps.get("s3.credentials_provider_type"),
@@ -718,17 +526,6 @@ public final class StorageAdapter {
                 origProps.get("iceberg.rest.credentials_provider_type"));
     }
 
-    private boolean isAzureOauth2() {
-        return 
"OAuth2".equalsIgnoreCase(origProps.getOrDefault("azure.auth_type", 
"SharedKey"));
-    }
-
-    private boolean isGcsAnonymous() {
-        if (!"GCS".equals(providerKey) || !(spi instanceof 
S3CompatibleFileSystemProperties)) {
-            return false;
-        }
-        return !((S3CompatibleFileSystemProperties) 
spi).hasStaticCredentials();
-    }
-
     /**
      * Align fe-core AzureProperties.initNormalizeAndCheckProps: the temporary 
fe-core-only
      * restriction that OAuth2 is supported only for the Iceberg REST catalog. 
This check reads
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/load/NereidsFileGroupInfo.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/load/NereidsFileGroupInfo.java
index b170d4e60c6..2713365cf28 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/load/NereidsFileGroupInfo.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/load/NereidsFileGroupInfo.java
@@ -26,6 +26,7 @@ import org.apache.doris.common.AnalysisException;
 import org.apache.doris.common.Config;
 import org.apache.doris.common.Pair;
 import org.apache.doris.common.UserException;
+import org.apache.doris.common.util.LocationPath;
 import org.apache.doris.common.util.Util;
 import org.apache.doris.datasource.scan.FederationBackendPolicy;
 import org.apache.doris.datasource.scan.FileGroupInfo;
@@ -48,11 +49,9 @@ import org.apache.doris.thrift.TUniqueKeyUpdateMode;
 
 import com.google.common.base.Preconditions;
 import com.google.common.collect.Lists;
-import org.apache.hadoop.fs.Path;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
-import java.net.URI;
 import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.List;
@@ -430,8 +429,9 @@ public class NereidsFileGroupInfo {
             rangeDesc.setColumnsFromPathKeys(columnsFromPathKeys);
             rangeDesc.setColumnsFromPathIsNull(columnsFromPathIsNull);
             if (getFileType() == TFileType.FILE_HDFS) {
-                URI fileUri = new Path(fileStatus.path).toUri();
-                rangeDesc.setFsName(fileUri.getScheme() + "://" + 
fileUri.getAuthority());
+                // LocationPath parses scheme and authority into fsIdentifier 
in exactly this
+                // "scheme://authority" shape, without pulling in hadoop.
+                
rangeDesc.setFsName(LocationPath.of(fileStatus.path).getFsIdentifier());
             }
         } else {
             // for stream load
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/cloud/stage/StageGlobTest.java 
b/fe/fe-core/src/test/java/org/apache/doris/cloud/stage/StageGlobTest.java
new file mode 100644
index 00000000000..7ab3386da78
--- /dev/null
+++ b/fe/fe-core/src/test/java/org/apache/doris/cloud/stage/StageGlobTest.java
@@ -0,0 +1,164 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.cloud.stage;
+
+import org.apache.doris.common.DdlException;
+import org.apache.doris.common.Pair;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Covers the glob analysis behind {@code COPY INTO ... FROM @stage('<glob>')}.
+ *
+ * <p>{@code analyzeGlob} turns a user glob into (prefix, hasWildcard) pairs: 
the prefix is what
+ * gets listed from the object store, and the flag says whether the listing 
still has to be
+ * filtered. Both matter for correctness, not just performance -- too short a 
prefix lists a whole
+ * bucket, too long a prefix silently skips files the user asked for.
+ *
+ * <p>The expectations are literals rather than a comparison against hadoop's 
{@code GlobExpander}
+ * / {@code GlobFilter}, which {@link GlobPatterns} was ported from. 
Equivalence to those classes
+ * was established with a differential run over the cases below plus 20k 
fuzzed globs (matching
+ * results, exception types and exception messages); pinning literals here 
keeps that behaviour
+ * from drifting without making fe-core tests depend on hadoop again.
+ */
+public class StageGlobTest {
+
+    private static String render(List<Pair<String, Boolean>> pairs) {
+        return pairs.stream().map(p -> p.first + "|" + 
p.second).collect(Collectors.joining(", "));
+    }
+
+    private static String analyze(String glob) throws DdlException {
+        return render(StageUtil.analyzeGlob("qid", glob));
+    }
+
+    @Test
+    public void literalGlobListsExactlyThatKey() throws DdlException {
+        // No wildcard anywhere: the whole path is the prefix and no filtering 
is needed.
+        Assertions.assertEquals("a/b/c.csv|false", analyze("a/b/c.csv"));
+        // A backslash escape makes '*' an ordinary character, so this is 
still a literal key.
+        Assertions.assertEquals("a*b|false", analyze("a\\*b"));
+    }
+
+    @Test
+    public void prefixStopsAtTheFirstWildcardCharacter() throws DdlException {
+        // The prefix must keep every leading literal component, otherwise the 
listing widens to
+        // the whole bucket.
+        Assertions.assertEquals("data/|true", analyze("data/*.parquet"));
+        Assertions.assertEquals("dt=2026-07-31/|true", 
analyze("dt=2026-07-31/*"));
+        // ... and it must also keep the literal head *inside* the first 
wildcard component.
+        Assertions.assertEquals("part-|true", analyze("part-?????.orc"));
+        Assertions.assertEquals("dir/sub/pre|true", 
analyze("dir/sub/pre*post/x"));
+        // Wildcard in the very first component leaves nothing to narrow the 
listing with.
+        Assertions.assertEquals("|true", analyze("*.csv"));
+        Assertions.assertEquals("|true", analyze("[abc]/x"));
+    }
+
+    @Test
+    public void nullAndEmptyGlobsDifferInWhetherFilteringIsNeeded() throws 
DdlException {
+        // No pattern at all -> list everything under the stage and keep it 
all.
+        Assertions.assertEquals("|true", analyze(null));
+        // An empty pattern is a literal, so nothing needs filtering.
+        Assertions.assertEquals("|false", analyze(""));
+    }
+
+    @Test
+    public void braceGroupWithSlashBecomesOneListingPerAlternative() throws 
DdlException {
+        // This is the reason brace expansion runs before prefix analysis: 
each alternative is a
+        // different object-store prefix, so one glob must produce two 
listings.
+        Assertions.assertEquals("logs/2026/01/|true, logs/2026/02/|true",
+                analyze("logs/{2026/01,2026/02}/*.csv"));
+        Assertions.assertEquals("a/b|false, c/d|false", analyze("{a/b,c/d}"));
+    }
+
+    @Test
+    public void braceGroupWithoutSlashStaysInsideOneComponent() throws 
DdlException {
+        // No slash inside the braces -> no expansion; the component is simply 
wildcard-bearing.
+        Assertions.assertEquals("pre|true", analyze("pre{a,b}post"));
+        Assertions.assertEquals("|true", analyze("{a,b}.csv"));
+    }
+
+    @Test
+    public void malformedGlobsAreRejectedRatherThanListedFromAWrongPrefix() {
+        // Validation parity is the point: without it these would silently 
degrade into a prefix
+        // listing instead of failing the statement.
+        assertRejected("a[b", "Unclosed character class");
+        assertRejected("a{b", "Unclosed group");
+        assertRejected("a\\", "An escaped character does not present");
+        // Character-class errors are left to the regex engine, exactly as 
hadoop did.
+        assertRejected("[z-a]", "invalid character class range");
+    }
+
+    private static void assertRejected(String glob, String expectedFragment) {
+        DdlException thrown = Assertions.assertThrows(DdlException.class,
+                () -> StageUtil.analyzeGlob("qid", glob));
+        Assertions.assertTrue(thrown.getMessage().contains(expectedFragment),
+                "expected <" + expectedFragment + "> in: " + 
thrown.getMessage());
+        Assertions.assertTrue(thrown.getMessage().contains("Failed to analyze 
glob: " + glob),
+                thrown.getMessage());
+    }
+
+    @Test
+    public void hasWildcardCountsOnlyTheFourGlobMetacharacters() throws 
Exception {
+        Assertions.assertFalse(GlobPatterns.hasWildcard("abc"));
+        Assertions.assertFalse(GlobPatterns.hasWildcard(""));
+        Assertions.assertTrue(GlobPatterns.hasWildcard("a*"));
+        Assertions.assertTrue(GlobPatterns.hasWildcard("a?"));
+        Assertions.assertTrue(GlobPatterns.hasWildcard("a[b]"));
+        Assertions.assertTrue(GlobPatterns.hasWildcard("a{b}"));
+        // An escaped metacharacter is a literal ...
+        Assertions.assertFalse(GlobPatterns.hasWildcard("a\\*b"));
+        // ... and the closing/separator characters are not wildcards on their 
own.
+        Assertions.assertFalse(GlobPatterns.hasWildcard("a,b"));
+        Assertions.assertFalse(GlobPatterns.hasWildcard("a}b"));
+        Assertions.assertFalse(GlobPatterns.hasWildcard("a]b"));
+    }
+
+    @Test
+    public void expandFlattensOnlyBraceGroupsContainingASlash() throws 
Exception {
+        // The four examples hadoop's GlobExpander javadoc pins, kept as the 
port's contract.
+        Assertions.assertEquals(List.of("pa/bs", "pc/ds"), 
GlobPatterns.expand("p{a/b,c/d}s"));
+        Assertions.assertEquals(List.of("a/b", "c/d", "{e,f}"), 
GlobPatterns.expand("{a/b,c/d,{e,f}}"));
+        Assertions.assertEquals(List.of("{a,b}/b", "{a,b}/c/d", "{a,b}/e/f"),
+                GlobPatterns.expand("{a,b}/{b,{c/d,e/f}}"));
+        Assertions.assertEquals(List.of("{a,b}/c/d"), 
GlobPatterns.expand("{a,b}/{c/\\d}"));
+        // Slash-free groups are left for per-component wildcard handling.
+        Assertions.assertEquals(List.of("{a,b}.csv"), 
GlobPatterns.expand("{a,b}.csv"));
+        Assertions.assertEquals(List.of("no-braces"), 
GlobPatterns.expand("no-braces"));
+    }
+
+    /**
+     * Pins a PRE-EXISTING defect so the hadoop-removal port is provably 
behaviour-preserving --
+     * this is documentation of current behaviour, not an endorsement of it.
+     *
+     * <p>{@code x/{y,z}} should yield two prefixes but yields only {@code 
x/y}: the group has no
+     * slash, so it survives expansion and reaches {@code 
StageUtil.splitByComma}, whose closing
+     * {@code if (start != str.length() - 1)} drops the final alternative 
whenever it is a single
+     * character. The bug is in Doris's own helper, not in the ported hadoop 
code, and predates
+     * this change; fixing it changes which files COPY INTO reads and belongs 
in its own commit.
+     */
+    @Test
+    public void knownDefectSingleCharTrailingAlternativeIsDropped() throws 
DdlException {
+        Assertions.assertEquals("x/y|false", analyze("x/{y,z}"));
+        // Two characters instead of one and the alternative survives, 
confirming the cause.
+        Assertions.assertEquals("x/y|false, x/zz|false", analyze("x/{y,zz}"));
+    }
+}
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/datasource/split/FileSplitterTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/datasource/split/FileSplitterTest.java
index 9931edb19cc..fcfd336b476 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/datasource/split/FileSplitterTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/datasource/split/FileSplitterTest.java
@@ -20,7 +20,6 @@ package org.apache.doris.datasource.split;
 import org.apache.doris.common.util.LocationPath;
 import org.apache.doris.spi.Split;
 
-import org.apache.hadoop.fs.BlockLocation;
 import org.junit.Assert;
 import org.junit.Test;
 
@@ -36,7 +35,7 @@ public class FileSplitterTest {
     @Test
     public void testNonSplittableCompressedFileProducesSingleSplit() throws 
Exception {
         LocationPath loc = LocationPath.of("hdfs://example.com/path/file.gz");
-        BlockLocation[] locations = new BlockLocation[]{new 
BlockLocation(null, new String[]{"h1"}, 0L, 10 * MB)};
+        FileBlockLocation[] locations = new FileBlockLocation[]{new 
FileBlockLocation(new String[]{"h1"}, 0L, 10 * MB)};
         FileSplitter fileSplitter = new FileSplitter(32 * MB, 64 * MB, 
DEFAULT_INITIAL_SPLITS);
         List<Split> splits = fileSplitter.splitFile(
                 loc,
@@ -58,7 +57,7 @@ public class FileSplitterTest {
     @Test
     public void testEmptyBlockLocationsProducesSingleSplitAndNullHosts() 
throws Exception {
         LocationPath loc = LocationPath.of("hdfs://example.com/path/file");
-        BlockLocation[] locations = new BlockLocation[0];
+        FileBlockLocation[] locations = new FileBlockLocation[0];
         FileSplitter fileSplitter = new FileSplitter(32 * MB, 64 * MB, 
DEFAULT_INITIAL_SPLITS);
         List<Split> splits = fileSplitter.splitFile(
                 loc,
@@ -81,7 +80,7 @@ public class FileSplitterTest {
     public void 
testSplittableSingleBigBlockProducesExpectedSplitsWithInitialSmallChunks() 
throws Exception {
         LocationPath loc = LocationPath.of("hdfs://example.com/path/bigfile");
         long length = 200 * MB;
-        BlockLocation[] locations = new BlockLocation[]{new 
BlockLocation(null, new String[]{"h1"}, 0L, length)};
+        FileBlockLocation[] locations = new FileBlockLocation[]{new 
FileBlockLocation(new String[]{"h1"}, 0L, length)};
         // set maxInitialSplits to 2 to force the first two splits to be small.
         FileSplitter fileSplitter = new FileSplitter(32 * MB, 64 * MB, 2);
         List<Split> splits = fileSplitter.splitFile(
@@ -110,13 +109,48 @@ public class FileSplitterTest {
         Assert.assertEquals(0, fileSplitter.getRemainingInitialSplitNum());
     }
 
+    @Test
+    public void testNullBlockLocationsSplitLikeOneWholeFileBlock() throws 
Exception {
+        // The only production caller (TVFScanNode) always passes null block 
locations, so this is the
+        // shape that actually ships. Null must degrade to a single synthetic 
block spanning the whole
+        // file -- not to "no splits" and not to one unsplit range -- which is 
why the expected sizes
+        // below are identical to 
testSplittableSingleBigBlockProducesExpectedSplitsWithInitialSmallChunks
+        // with the same splitter settings. A broken synthetic block would 
silently change how many
+        // ranges the BE receives for every TVF scan.
+        LocationPath loc = LocationPath.of("hdfs://example.com/path/bigfile");
+        long length = 200 * MB;
+        FileSplitter fileSplitter = new FileSplitter(32 * MB, 64 * MB, 2);
+        List<Split> splits = fileSplitter.splitFile(
+                loc,
+                0L,
+                null,
+                length,
+                0L,
+                true,
+                Collections.emptyList(),
+                FileSplit.FileSplitCreator.DEFAULT);
+
+        long[] expected = new long[]{32 * MB, 32 * MB, 64 * MB, 36 * MB, 36 * 
MB};
+        Assert.assertEquals(expected.length, splits.size());
+        long sum = 0L;
+        for (int i = 0; i < expected.length; i++) {
+            FileSplit s = (FileSplit) splits.get(i);
+            Assert.assertEquals(expected[i], s.getLength());
+            sum += s.getLength();
+            // No locality information is available, so no split may claim a 
host.
+            Assert.assertNotNull(s.getHosts());
+            Assert.assertEquals(0, s.getHosts().length);
+        }
+        Assert.assertEquals(length, sum);
+    }
+
     @Test
     public void testMultiBlockSplitsAndHostPreservation() throws Exception {
         LocationPath loc = 
LocationPath.of("hdfs://example.com/path/twoblocks");
         long len = 96 * MB;
-        BlockLocation[] locations = new BlockLocation[]{
-                new BlockLocation(null, new String[]{"h1"}, 0L, 48 * MB),
-                new BlockLocation(null, new String[]{"h2"}, 48 * MB, 48 * MB)
+        FileBlockLocation[] locations = new FileBlockLocation[]{
+                new FileBlockLocation(new String[]{"h1"}, 0L, 48 * MB),
+                new FileBlockLocation(new String[]{"h2"}, 48 * MB, 48 * MB)
         };
         FileSplitter fileSplitter = new FileSplitter(32 * MB, 64 * MB, 0);
         List<Split> splits = fileSplitter.splitFile(
@@ -141,9 +175,9 @@ public class FileSplitterTest {
     public void testZeroLengthBlockIsSkipped() throws Exception {
         LocationPath loc = 
LocationPath.of("hdfs://example.com/path/zeroblock");
         long length = 10 * MB;
-        BlockLocation[] locations = new BlockLocation[]{
-                new BlockLocation(null, new String[]{"h1"}, 0L, 0L),
-                new BlockLocation(null, new String[]{"h1"}, 0L, 10 * MB)
+        FileBlockLocation[] locations = new FileBlockLocation[]{
+                new FileBlockLocation(new String[]{"h1"}, 0L, 0L),
+                new FileBlockLocation(new String[]{"h1"}, 0L, 10 * MB)
         };
         FileSplitter fileSplitter = new FileSplitter(32 * MB, 64 * MB, 
DEFAULT_INITIAL_SPLITS);
         List<Split> splits = fileSplitter.splitFile(
@@ -164,7 +198,7 @@ public class FileSplitterTest {
     @Test
     public void testNonSplittableFlagDecrementsCounter() throws Exception {
         LocationPath loc = LocationPath.of("hdfs://example.com/path/file.gz");
-        BlockLocation[] locations = new BlockLocation[]{new 
BlockLocation(null, new String[]{"h1"}, 0L, 10 * MB)};
+        FileBlockLocation[] locations = new FileBlockLocation[]{new 
FileBlockLocation(new String[]{"h1"}, 0L, 10 * MB)};
         FileSplitter fileSplitter = new FileSplitter(32 * MB, 64 * MB, 2);
         List<Split> splits = fileSplitter.splitFile(
                 loc,
@@ -181,7 +215,7 @@ public class FileSplitterTest {
     @Test
     public void testNullRemainingInitialSplitIsAllowed() throws Exception {
         LocationPath loc = LocationPath.of("hdfs://example.com/path/somefile");
-        BlockLocation[] locations = new BlockLocation[]{new 
BlockLocation(null, new String[]{"h1"}, 0L, 10 * MB)};
+        FileBlockLocation[] locations = new FileBlockLocation[]{new 
FileBlockLocation(new String[]{"h1"}, 0L, 10 * MB)};
         FileSplitter fileSplitter = new FileSplitter(32 * MB, 64 * MB, 
DEFAULT_INITIAL_SPLITS);
         List<Split> splits = fileSplitter.splitFile(
                 loc,
@@ -198,7 +232,7 @@ public class FileSplitterTest {
     @Test
     public void testZeroLengthFileProducesNoSplits() throws Exception {
         LocationPath loc = 
LocationPath.of("hdfs://example.com/path/emptyfile");
-        BlockLocation[] locations = new BlockLocation[]{new 
BlockLocation(null, new String[]{"h1"}, 0L, 0L)};
+        FileBlockLocation[] locations = new FileBlockLocation[]{new 
FileBlockLocation(new String[]{"h1"}, 0L, 0L)};
         FileSplitter fileSplitter = new FileSplitter(32 * MB, 64 * MB, 
DEFAULT_INITIAL_SPLITS);
         // Non-splittable zero-length file
         List<Split> splits = fileSplitter.splitFile(
@@ -222,7 +256,7 @@ public class FileSplitterTest {
     @Test
     public void testSmallFileNoSplit() throws Exception {
         LocationPath loc = LocationPath.of("hdfs://example.com/path/small");
-        BlockLocation[] locations = new BlockLocation[]{new 
BlockLocation(null, new String[]{"h1"}, 0L, 2 * MB)};
+        FileBlockLocation[] locations = new FileBlockLocation[]{new 
FileBlockLocation(new String[]{"h1"}, 0L, 2 * MB)};
         FileSplitter fileSplitter = new FileSplitter(32 * MB, 64 * MB, 
DEFAULT_INITIAL_SPLITS);
         List<Split> splits = fileSplitter.splitFile(
                 loc,
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/planner/FederationBackendPolicyTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/planner/FederationBackendPolicyTest.java
index 9c04b73d61c..34ec8355a96 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/planner/FederationBackendPolicyTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/planner/FederationBackendPolicyTest.java
@@ -32,7 +32,6 @@ import org.apache.doris.system.SystemInfoService;
 import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.ListMultimap;
 import com.google.common.collect.Multimap;
-import org.apache.hadoop.fs.Path;
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
@@ -155,10 +154,10 @@ public class FederationBackendPolicyTest {
             for (Split split : assignedSplits) {
                 FileSplit fileSplit = (FileSplit) split;
                 ++totalSplitNum;
-                if (fileSplit.getPath().getPath().equals(new 
Path("hdfs://HDFS8000871/usr/hive/warehouse/clickbench.db/hits_orc/part-00000-3e24f7d5-f658-4a80-a168-7b215c5a35bf-c000.snappy.orc")))
 {
+                if 
(fileSplit.getPath().getNormalizedLocation().equals("hdfs://HDFS8000871/usr/hive/warehouse/clickbench.db/hits_orc/part-00000-3e24f7d5-f658-4a80-a168-7b215c5a35bf-c000.snappy.orc"))
 {
                     Assert.assertEquals("172.30.0.100", backend.getHost());
                     checkedLocalSplit.add(true);
-                } else if (fileSplit.getPath().getPath().equals(new 
Path("hdfs://HDFS8000871/usr/hive/warehouse/clickbench.db/hits_orc/part-00003-3e24f7d5-f658-4a80-a168-7b215c5a35bf-c000.snappy.orc")))
 {
+                } else if 
(fileSplit.getPath().getNormalizedLocation().equals("hdfs://HDFS8000871/usr/hive/warehouse/clickbench.db/hits_orc/part-00003-3e24f7d5-f658-4a80-a168-7b215c5a35bf-c000.snappy.orc"))
 {
                     Assert.assertEquals("172.30.0.106", backend.getHost());
                     checkedLocalSplit.add(true);
                 }
diff --git a/fe/fe-filesystem/fe-filesystem-azure/pom.xml 
b/fe/fe-filesystem/fe-filesystem-azure/pom.xml
index fae9d3a230f..cb76d8c2f09 100644
--- a/fe/fe-filesystem/fe-filesystem-azure/pom.xml
+++ b/fe/fe-filesystem/fe-filesystem-azure/pom.xml
@@ -52,6 +52,43 @@ under the License.
             <groupId>org.apache.commons</groupId>
             <artifactId>commons-lang3</artifactId>
         </dependency>
+        <!-- OAuth2 backend map only (toMap): the legacy fe-core 
AzureProperties handed BE a whole
+             org.apache.hadoop.conf.Configuration, so the map carries hadoop's 
core-default.xml
+             plus any core-site.xml on the FE classpath (start_fe.sh puts both 
${DORIS_HOME}/conf
+             and ${HADOOP_CONF_DIR} there). Reproducing that key-for-key needs 
the real class.
+
+             Bundled (not provided), matching 
fe-filesystem-hdfs/-jfs/-oss-hdfs, so the plugin does
+             not depend on the host shipping hadoop. Note this copy is a 
FALLBACK, not the active
+             one: FileSystemPluginManager.FS_PARENT_FIRST_PREFIXES lists 
"org.apache.hadoop.", so
+             ChildFirstClassLoader resolves hadoop from the parent whenever 
fe/lib carries it and
+             only reaches these jars through the findClass fallback when it 
does not. That ordering
+             is what keeps a single Class object in play today, i.e. no 
classloader split-brain.
+
+             Logging exclusions mirror fe-filesystem-hdfs-base: the assembly 
drops org.slf4j:* and
+             org.apache.logging.log4j:*, but log4j:log4j (1.x) is a different 
groupId and would
+             otherwise land in the plugin lib/ and fight the host's logging. 
-->
+        <dependency>
+            <groupId>org.apache.hadoop</groupId>
+            <artifactId>hadoop-common</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>commons-collections</groupId>
+                    <artifactId>commons-collections</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.apache.commons</groupId>
+                    <artifactId>commons-compress</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.slf4j</groupId>
+                    <artifactId>slf4j-log4j12</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>log4j</groupId>
+                    <artifactId>log4j</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
 
         <!-- Azure SDK (versions managed by azure-sdk-bom in fe/pom.xml 
dependencyManagement) -->
         <dependency>
diff --git 
a/fe/fe-filesystem/fe-filesystem-azure/src/main/java/org/apache/doris/filesystem/azure/AzureFileSystemProperties.java
 
b/fe/fe-filesystem/fe-filesystem-azure/src/main/java/org/apache/doris/filesystem/azure/AzureFileSystemProperties.java
index c7a65df4b69..33b611c7b76 100644
--- 
a/fe/fe-filesystem/fe-filesystem-azure/src/main/java/org/apache/doris/filesystem/azure/AzureFileSystemProperties.java
+++ 
b/fe/fe-filesystem/fe-filesystem-azure/src/main/java/org/apache/doris/filesystem/azure/AzureFileSystemProperties.java
@@ -27,7 +27,9 @@ import 
org.apache.doris.foundation.property.ConnectorPropertiesUtils;
 import org.apache.doris.foundation.property.ConnectorProperty;
 import org.apache.doris.foundation.property.ParamRules;
 
+import org.apache.commons.lang3.BooleanUtils;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.hadoop.conf.Configuration;
 
 import java.lang.reflect.Field;
 import java.net.URI;
@@ -235,7 +237,7 @@ public final class AzureFileSystemProperties
         // S3-style params. Mirrors legacy 
AzureProperties.getBackendConfigProperties();
         // native-SDK OAuth2 support may replace this in the future.
         if (isOauth2Auth()) {
-            return toHadoopConfigurationMap();
+            return oauth2BackendProperties();
         }
         Map<String, String> s3Props = new HashMap<>();
         s3Props.put("AWS_ENDPOINT", endpoint);
@@ -275,6 +277,51 @@ public final class AzureFileSystemProperties
         return Collections.unmodifiableMap(cfg);
     }
 
+    /**
+     * BE property map for OAuth2, key-for-key equal to what legacy fe-core
+     * {@code AzureProperties.getBackendConfigProperties()} produced: it 
dumped its whole
+     * {@link Configuration}, so on top of {@link #toHadoopConfigurationMap()} 
the map also carries
+     * hadoop's {@code core-default.xml} and any {@code core-site.xml} 
reachable on the FE
+     * classpath ({@code start_fe.sh} puts {@code ${DORIS_HOME}/conf} and 
{@code ${HADOOP_CONF_DIR}}
+     * there, so this is a real operator-facing channel, not just hadoop's 
built-in defaults).
+     *
+     * <p>The live consumer is Microsoft Fabric OneLake: {@code 
LocationPath.getTFileTypeForBE()}
+     * routes {@code abfs[s]://...dfs.fabric.microsoft.com} to {@code 
FILE_HDFS}, and BE's
+     * {@code hdfs_builder.cpp} feeds every entry of this map into its JNI 
hadoop builder — which is
+     * how the {@code fs.azure.account.oauth2.*} keys reach the ABFS connector.
+     *
+     * <p>Ordering is load-bearing and mirrors the legacy sequence: hadoop 
defaults first, then the
+     * provider's own {@code fs.azure.*} view, then user {@code fs.*} 
passthrough (so an explicit
+     * user value wins), then cache-disable normalization last.
+     *
+     * <p>The plugin bundles hadoop, but {@code 
FileSystemPluginManager.FS_PARENT_FIRST_PREFIXES}
+     * makes {@code org.apache.hadoop.} parent-first, so whenever the FE host 
ships hadoop this
+     * resolves to the host copy and the bundled one is only a {@code 
findClass} fallback. That also
+     * means the XML defaults below come from wherever the context classloader 
finds them — hadoop's
+     * {@code Configuration} looks up {@code core-default.xml}/{@code 
core-site.xml} through the
+     * TCCL, not through this class's loader. A host that stops shipping 
hadoop would therefore need
+     * the TCCL pinned to the plugin loader here, not just the bundled jars.
+     */
+    private Map<String, String> oauth2BackendProperties() {
+        Configuration conf = new Configuration();
+        toHadoopConfigurationMap().forEach(conf::set);
+        rawProperties.forEach((key, value) -> {
+            if (key.startsWith("fs.") && StringUtils.isNotBlank(value)) {
+                conf.set(key, value);
+            }
+        });
+        for (String scheme : legacyCacheSchemes()) {
+            String key = "fs." + scheme + ".impl.disable.cache";
+            String userValue = rawProperties.get(key);
+            // An explicit user value wins but is normalized to true/false 
("yes"/"1" would reach BE
+            // verbatim through the fs.* passthrough above otherwise).
+            conf.setBoolean(key, StringUtils.isNotBlank(userValue) ? 
BooleanUtils.toBoolean(userValue) : true);
+        }
+        Map<String, String> dump = new HashMap<>();
+        conf.forEach(entry -> dump.put(entry.getKey(), entry.getValue()));
+        return Collections.unmodifiableMap(dump);
+    }
+
     public String getEndpoint() {
         return endpoint;
     }
diff --git 
a/fe/fe-filesystem/fe-filesystem-azure/src/test/java/org/apache/doris/filesystem/azure/AzureFileSystemPropertiesTest.java
 
b/fe/fe-filesystem/fe-filesystem-azure/src/test/java/org/apache/doris/filesystem/azure/AzureFileSystemPropertiesTest.java
index 23283968b3e..889d4c14dd0 100644
--- 
a/fe/fe-filesystem/fe-filesystem-azure/src/test/java/org/apache/doris/filesystem/azure/AzureFileSystemPropertiesTest.java
+++ 
b/fe/fe-filesystem/fe-filesystem-azure/src/test/java/org/apache/doris/filesystem/azure/AzureFileSystemPropertiesTest.java
@@ -117,6 +117,78 @@ class AzureFileSystemPropertiesTest {
         Assertions.assertEquals("azure", backendMap.get("provider"));
         Assertions.assertEquals("true", backendMap.get("use_path_style"));
         Assertions.assertFalse(backendMap.keySet().stream().anyMatch(keyName 
-> keyName.startsWith("AZURE_")));
+        // The hadoop Configuration dump is the OAuth2 arm ONLY: SharedKey 
must stay the exact
+        // 7-key S3-style map, so a regression that applies the dump 
unconditionally shows up here.
+        Assertions.assertEquals(7, backendMap.size(), backendMap.toString());
+    }
+
+    /**
+     * Pins the OAuth2 backend map that moved here from fe-core {@code 
StorageAdapter} (which built a
+     * hadoop {@code Configuration} and dumped it). BE's live consumer is 
Microsoft Fabric OneLake:
+     * {@code abfs[s]://...dfs.fabric.microsoft.com} routes to {@code 
FILE_HDFS} and every entry is
+     * fed into BE's JNI hadoop builder, so dropping the hadoop-resolved keys 
silently changes what
+     * the ABFS connector is configured with.
+     */
+    @Test
+    void toBackendProperties_oauth2DumpsHadoopResolvedConfig() {
+        AzureFileSystemProperties properties = 
AzureFileSystemProperties.of(Map.of(
+                "azure.endpoint", "account.blob.core.windows.net",
+                "azure.auth_type", "OAuth2",
+                "azure.oauth2_account_host", "myaccount.dfs.core.windows.net",
+                "azure.oauth2_client_id", "client-id",
+                "azure.oauth2_client_secret", "client-secret",
+                "azure.oauth2_server_uri", 
"https://login.microsoftonline.com/tenant/oauth2/token";));
+
+        Map<String, String> backendMap = 
properties.toBackendProperties().orElseThrow().toMap();
+
+        // 1. The OAuth config the ABFS connector actually authenticates with.
+        Assertions.assertEquals("OAuth",
+                
backendMap.get("fs.azure.account.auth.type.myaccount.dfs.core.windows.net"));
+        
Assertions.assertEquals("org.apache.hadoop.fs.azurebfs.oauth2.ClientCredsTokenProvider",
+                
backendMap.get("fs.azure.account.oauth.provider.type.myaccount.dfs.core.windows.net"));
+        Assertions.assertEquals("client-id",
+                
backendMap.get("fs.azure.account.oauth2.client.id.myaccount.dfs.core.windows.net"));
+        Assertions.assertEquals("client-secret",
+                
backendMap.get("fs.azure.account.oauth2.client.secret.myaccount.dfs.core.windows.net"));
+        
Assertions.assertEquals("https://login.microsoftonline.com/tenant/oauth2/token";,
+                
backendMap.get("fs.azure.account.oauth2.client.endpoint.myaccount.dfs.core.windows.net"));
+
+        // 2. hadoop core-default.xml is merged in. This is the whole reason 
the module compiles
+        //    against hadoop-common: a plain key-value map would carry the 
OAuth keys above but none
+        //    of these, and the change would be invisible to every other 
assertion.
+        Assertions.assertEquals("file:///", backendMap.get("fs.defaultFS"));
+        
Assertions.assertTrue(backendMap.containsKey("hadoop.security.authentication"), 
backendMap.toString());
+        Assertions.assertTrue(backendMap.size() > 100,
+                "expected a resolved hadoop config, got " + backendMap.size() 
+ " keys");
+
+        // 3. Never S3-style: OAuth2 has no AK/SK the BE S3 adapter could 
consume.
+        Assertions.assertFalse(backendMap.containsKey("AWS_ACCESS_KEY"), 
backendMap.toString());
+        Assertions.assertFalse(backendMap.containsKey("provider"), 
backendMap.toString());
+    }
+
+    @Test
+    void 
toBackendProperties_oauth2PassesUserFsKeysThroughAndNormalizesCacheFlags() {
+        AzureFileSystemProperties properties = 
AzureFileSystemProperties.of(Map.of(
+                "azure.endpoint", "account.blob.core.windows.net",
+                "azure.auth_type", "OAuth2",
+                "azure.oauth2_account_host", "myaccount.dfs.core.windows.net",
+                "azure.oauth2_client_id", "client-id",
+                "azure.oauth2_client_secret", "client-secret",
+                "azure.oauth2_server_uri", 
"https://login.microsoftonline.com/tenant/oauth2/token";,
+                // arbitrary user fs.* key, not azure-scoped: legacy passed 
the whole fs.* family through
+                "fs.azure.readaheadqueue.depth", "8",
+                // explicit cache flag in a spelling only BooleanUtils 
understands
+                "fs.abfss.impl.disable.cache", "no"));
+
+        Map<String, String> backendMap = 
properties.toBackendProperties().orElseThrow().toMap();
+
+        Assertions.assertEquals("8", 
backendMap.get("fs.azure.readaheadqueue.depth"));
+        // Explicit user value wins, but normalized to true/false — "no" must 
not reach BE verbatim.
+        Assertions.assertEquals("false", 
backendMap.get("fs.abfss.impl.disable.cache"));
+        // The other three legacy schemes keep the default.
+        Assertions.assertEquals("true", 
backendMap.get("fs.abfs.impl.disable.cache"));
+        Assertions.assertEquals("true", 
backendMap.get("fs.wasb.impl.disable.cache"));
+        Assertions.assertEquals("true", 
backendMap.get("fs.wasbs.impl.disable.cache"));
     }
 
     @Test
diff --git a/fe/fe-filesystem/fe-filesystem-obs/pom.xml 
b/fe/fe-filesystem/fe-filesystem-obs/pom.xml
index f4498f9d980..fdfbc0ae9ae 100644
--- a/fe/fe-filesystem/fe-filesystem-obs/pom.xml
+++ b/fe/fe-filesystem/fe-filesystem-obs/pom.xml
@@ -64,6 +64,41 @@ under the License.
             <artifactId>huaweicloud-sdk-iam</artifactId>
             <version>${huaweicloud-sdk-iam.version}</version>
         </dependency>
+        <!-- Supplies org.apache.hadoop.fs.obs.OBSFileSystem. Nothing here 
calls it: this module reaches OBS
+             through the native esdk SDK above, and the ONLY reference is the 
initialize=false
+             Class.forName probe in ObsFileSystemProperties, which decides 
whether toHadoopConfigurationMap
+             emits fs.obs.impl=OBSFileSystem or falls back to 
fs.obs.impl=S3AFileSystem. The probe resolves
+             against THIS module's own classloader, so the jar has to sit in 
this plugin for the probe to
+             tell the truth. Consumers that actually instantiate the class 
carry their own copy
+             (fe-connector-paimon, be-java-extensions/hadoop-deps); 
fe-connector-iceberg needs none because
+             it normalizes obs:// to s3 and goes through S3AFileSystem.
+             runtime scope keeps it off the compile classpath; plugin-zip.xml 
packs the runtime closure.
+
+             com/obs/* DUPLICATION — read before touching versions or artifact 
names. The -hw-46 jar is a
+             fat jar that inlines the whole esdk SDK (762 com/obs/* classes, 
identical to the
+             esdk-obs-java-optimised 3.21.8.2 content), and 
esdk-obs-java-bundle above ships 511 of which
+             510 collide. That is TWO copies of com.obs.services.ObsClient in 
this plugin's lib/, at
+             DIFFERENT versions (3.21.11 declared above vs 3.21.8.2 inlined 
here), and ObsObjStorage
+             compiles against the former. 
DirectoryPluginRuntimeManager.collectJars sorts lib/ jars
+             lexicographically, so "esdk-obs-java-bundle-*" precedes 
"hadoop-huaweicloud-*" and the
+             declared 3.21.11 wins — correct, but only because of that 
ordering. If either artifact is
+             renamed or the esdk coordinates change, re-check that the bundle 
still sorts first.
+             The transitive esdk-obs-java-optimised is excluded below: it is a 
third, redundant copy of
+             the same classes already inlined in the fat jar, and the only one 
that could shadow the
+             declared bundle if the sort order ever shifted.
+             NOTE: the -hw-46 artifact is NOT in Maven Central; it comes from 
the huawei-obs-sdk repo
+             declared in <repositories> below (fe-connector-paimon declares 
the same repo for the same dep). -->
+        <dependency>
+            <groupId>com.huaweicloud</groupId>
+            <artifactId>hadoop-huaweicloud</artifactId>
+            <scope>runtime</scope>
+            <exclusions>
+                <exclusion>
+                    <groupId>com.huaweicloud</groupId>
+                    <artifactId>esdk-obs-java-optimised</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
         <dependency>
             <groupId>org.junit.jupiter</groupId>
             <artifactId>junit-jupiter-api</artifactId>
@@ -76,6 +111,14 @@ under the License.
         </dependency>
     </dependencies>
 
+    <repositories>
+        <!-- for huawei obs sdk (hadoop-huaweicloud is not published to Maven 
Central) -->
+        <repository>
+            <id>huawei-obs-sdk</id>
+            
<url>https://repo.huaweicloud.com/repository/maven/huaweicloudsdk/</url>
+        </repository>
+    </repositories>
+
     <build>
         <finalName>doris-fe-filesystem-obs</finalName>
         <plugins>


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to