diff --git a/binary-compatibility-validator/android/api/binary-compatibility-validator-android.api b/binary-compatibility-validator/android/api/binary-compatibility-validator-android.api
index 866d647c9..ef63378e7 100644
--- a/binary-compatibility-validator/android/api/binary-compatibility-validator-android.api
+++ b/binary-compatibility-validator/android/api/binary-compatibility-validator-android.api
@@ -849,6 +849,8 @@ public abstract interface class net/mamoe/mirai/contact/file/AbsoluteFolder : ne
 	public abstract fun resolveFilesStream (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
 	public fun resolveFolder (Ljava/lang/String;)Lnet/mamoe/mirai/contact/file/AbsoluteFolder;
 	public abstract fun resolveFolder (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+	public fun resolveFolderById (Ljava/lang/String;)Lnet/mamoe/mirai/contact/file/AbsoluteFolder;
+	public abstract fun resolveFolderById (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
 	public fun uploadNewFile (Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/contact/file/AbsoluteFile;
 	public fun uploadNewFile (Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
 	public fun uploadNewFile (Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/ProgressionCallback;)Lnet/mamoe/mirai/contact/file/AbsoluteFile;
diff --git a/binary-compatibility-validator/api/binary-compatibility-validator.api b/binary-compatibility-validator/api/binary-compatibility-validator.api
index dc1438c42..258074e31 100644
--- a/binary-compatibility-validator/api/binary-compatibility-validator.api
+++ b/binary-compatibility-validator/api/binary-compatibility-validator.api
@@ -849,6 +849,8 @@ public abstract interface class net/mamoe/mirai/contact/file/AbsoluteFolder : ne
 	public abstract fun resolveFilesStream (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
 	public fun resolveFolder (Ljava/lang/String;)Lnet/mamoe/mirai/contact/file/AbsoluteFolder;
 	public abstract fun resolveFolder (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+	public fun resolveFolderById (Ljava/lang/String;)Lnet/mamoe/mirai/contact/file/AbsoluteFolder;
+	public abstract fun resolveFolderById (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
 	public fun uploadNewFile (Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/contact/file/AbsoluteFile;
 	public fun uploadNewFile (Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
 	public fun uploadNewFile (Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/ProgressionCallback;)Lnet/mamoe/mirai/contact/file/AbsoluteFile;
diff --git a/mirai-core-api/src/commonMain/kotlin/contact/file/AbsoluteFolder.kt b/mirai-core-api/src/commonMain/kotlin/contact/file/AbsoluteFolder.kt
index 3d46ec6b1..aa441e742 100644
--- a/mirai-core-api/src/commonMain/kotlin/contact/file/AbsoluteFolder.kt
+++ b/mirai-core-api/src/commonMain/kotlin/contact/file/AbsoluteFolder.kt
@@ -119,6 +119,15 @@ public interface AbsoluteFolder : AbsoluteFileFolder {
      */
     public suspend fun resolveFolder(name: String): AbsoluteFolder?
 
+    /**
+     * 获取一个已存在的 [AbsoluteFileFolder.id] 为 [id] 的子目录. 当该名称的子目录不存在时返回 `null`.
+     *
+     * @throws IllegalArgumentException 当 [id] 为空或无效时抛出
+     *
+     * @since 2.9.0
+     */
+    public suspend fun resolveFolderById(id: String): AbsoluteFolder?
+
     /**
      * 精确获取 [AbsoluteFile.id] 为 [id] 的文件. 在目标文件不存在时返回 `null`. 当 [deep] 为 `true` 时还会深入子目录查找.
      */
diff --git a/mirai-core/src/commonMain/kotlin/contact/file/AbsoluteFolderImpl.kt b/mirai-core/src/commonMain/kotlin/contact/file/AbsoluteFolderImpl.kt
index 81ad72ff3..45a26196f 100644
--- a/mirai-core/src/commonMain/kotlin/contact/file/AbsoluteFolderImpl.kt
+++ b/mirai-core/src/commonMain/kotlin/contact/file/AbsoluteFolderImpl.kt
@@ -369,6 +369,19 @@ internal class AbsoluteFolderImpl(
         return getItemsFlow().firstOrNull { it.folderInfo?.folderName == name }?.resolve() as AbsoluteFolder?
     }
 
+    override suspend fun resolveFolderById(id: String): AbsoluteFolder? {
+        if (name.isBlank()) throw IllegalArgumentException("folder id cannot be blank.")
+        if (!FileSystem.isLegal(id)) return null
+        if (id == AbsoluteFolder.ROOT_FOLDER_ID) return root // special case, not ambiguous — '/' always refers to root.
+        if (this.id != AbsoluteFolder.ROOT_FOLDER_ID) return null // reserved for future
+
+        // All folder ids start with '/'.
+        // Currently, only root folders can have children folders,
+        // and we don't know how the folderIds can be changed,
+        // so we force the receiver to be root for now, to provide forward-compatibility.
+        return root.impl().getItemsFlow().firstOrNull { it.folderInfo?.folderId == id }?.resolve() as AbsoluteFolder?
+    }
+
     override suspend fun resolveFileById(id: String, deep: Boolean): AbsoluteFile? {
         if (id == "/" || id.isEmpty()) throw IllegalArgumentException("Illegal file id: $id")
         getItemsFlow().filter { it.fileInfo?.fileId == id }.map { it.resolve() as AbsoluteFile }.firstOrNull()
diff --git a/mirai-core/src/commonTest/kotlin/contact/file/AbsoluteFolderTest.kt b/mirai-core/src/commonTest/kotlin/contact/file/AbsoluteFolderTest.kt
new file mode 100644
index 000000000..b69cc9a7d
--- /dev/null
+++ b/mirai-core/src/commonTest/kotlin/contact/file/AbsoluteFolderTest.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2019-2021 Mamoe Technologies and contributors.
+ *
+ * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
+ * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
+ *
+ * https://github.com/mamoe/mirai/blob/dev/LICENSE
+ */
+
+@file:JvmBlockingBridge
+
+package net.mamoe.mirai.internal.contact.file
+
+import net.mamoe.kjbb.JvmBlockingBridge
+import net.mamoe.mirai.internal.MockBot
+import net.mamoe.mirai.internal.network.notice.BotAware
+import net.mamoe.mirai.internal.network.protocol.data.proto.GroupFileCommon
+import net.mamoe.mirai.internal.notice.processors.GroupExtensions
+import net.mamoe.mirai.internal.test.AbstractTest
+import org.junit.jupiter.api.Test
+import kotlin.test.assertEquals
+
+internal class AbsoluteFolderTest : AbstractTest(), BotAware, GroupExtensions {
+    override val bot = MockBot { }
+    val group = bot.addGroup(1L, 2L)
+    private val root = group.files.root
+
+    @Test
+    suspend fun `resolveFolderById always returns null if it is not root`() {
+        val child = root.impl().createChildFolder(
+            GroupFileCommon.FolderInfo(
+                folderId = "/f-1",
+                folderName = "name"
+            )
+        )
+        assertEquals(null, child.resolveFolderById("/anything"))
+    }
+
+    @Test
+    suspend fun `resolveFolderById always returns root for slash`() {
+        val child = root.impl().createChildFolder(
+            GroupFileCommon.FolderInfo(
+                folderId = "/f-1",
+                folderName = "name"
+            )
+        )
+        assertEquals(root, root.resolveFolderById("/"))
+        assertEquals(root, child.resolveFolderById("/"))
+    }
+}
\ No newline at end of file