diff --git a/mirai-japt/README.md b/mirai-japt/README.md
new file mode 100644
index 000000000..a9627a246
--- /dev/null
+++ b/mirai-japt/README.md
@@ -0,0 +1,9 @@
+
+# mirai-japt
+
+Mirai Java Apt  
+
+提供一些阻塞/异步/RxJava API 来让 Java 调用 Mirai 的挂起函数 API 更容易  
+提供 Utils 类来让 Java 调用 Mirai 的内联方法更容易
+
+该模块暂未完成.
\ No newline at end of file
diff --git a/mirai-japt/build.gradle.kts b/mirai-japt/build.gradle.kts
new file mode 100644
index 000000000..2f7cf3c43
--- /dev/null
+++ b/mirai-japt/build.gradle.kts
@@ -0,0 +1,44 @@
+plugins {
+    kotlin("jvm")
+    java
+}
+
+val kotlinVersion: String by rootProject.ext
+val atomicFuVersion: String by rootProject.ext
+val coroutinesVersion: String by rootProject.ext
+val kotlinXIoVersion: String by rootProject.ext
+val coroutinesIoVersion: String by rootProject.ext
+val serializationVersion: String by rootProject.ext
+
+val klockVersion: String by rootProject.ext
+val ktorVersion: String by rootProject.ext
+
+kotlin {
+    sourceSets {
+        all {
+            languageSettings.enableLanguageFeature("InlineClasses")
+            languageSettings.useExperimentalAnnotation("kotlin.Experimental")
+        }
+    }
+}
+
+fun kotlinx(id: String, version: String) = "org.jetbrains.kotlinx:kotlinx-$id:$version"
+
+fun ktor(id: String, version: String) = "io.ktor:ktor-$id:$version"
+
+dependencies {
+    api(project(":mirai-core"))
+    runtimeOnly(files("../mirai-core/build/classes/kotlin/jvm/main")) // classpath is not added correctly by IDE
+
+    api(group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-javafx", version = "1.3.2")
+
+    api(kotlin("stdlib", kotlinVersion))
+    api(kotlinx("io-jvm", kotlinXIoVersion))
+    api(kotlinx("io", kotlinXIoVersion))
+    api(kotlinx("coroutines-io", coroutinesIoVersion))
+    api(kotlinx("coroutines-core", coroutinesVersion))
+}
+
+tasks.withType<JavaCompile>() {
+    options.encoding = "UTF-8"
+}
\ No newline at end of file
diff --git a/mirai-japt/mirai-japt.postfixTemplates b/mirai-japt/mirai-japt.postfixTemplates
new file mode 100644
index 000000000..9df0de9fd
--- /dev/null
+++ b/mirai-japt/mirai-japt.postfixTemplates
@@ -0,0 +1,4 @@
+# Contact
+
+.blocking : 阻塞式包装
+    net.mamoe.mirai.contact.QQ [net.mamoe.mirai.japt.BlockingContacts] -> BlockingQQ. BlockingContacts.createBlocking($var$)
diff --git a/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingContact.java b/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingContact.java
new file mode 100644
index 000000000..dcc1585cb
--- /dev/null
+++ b/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingContact.java
@@ -0,0 +1,26 @@
+package net.mamoe.mirai.japt;
+
+import net.mamoe.mirai.Bot;
+import net.mamoe.mirai.message.MessageChain;
+import org.jetbrains.annotations.NotNull;
+
+@SuppressWarnings("unused")
+public interface BlockingContact {
+    /**
+     * 这个联系人所属 [Bot]
+     */
+    @NotNull
+    Bot getBot();
+
+    /**
+     * 可以是 QQ 号码或者群号码 [GroupId].
+     */
+    long getId();
+
+    /**
+     * 向这个对象发送消息.
+     * <p>
+     * 速度太快会被服务器屏蔽(无响应). 在测试中不延迟地发送 6 条消息就会被屏蔽之后的数据包 1 秒左右.
+     */
+    void sendMessage(@NotNull MessageChain messages);
+}
diff --git a/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingContacts.java b/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingContacts.java
new file mode 100644
index 000000000..de7a3b9cf
--- /dev/null
+++ b/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingContacts.java
@@ -0,0 +1,22 @@
+package net.mamoe.mirai.japt;
+
+import net.mamoe.mirai.contact.Group;
+import net.mamoe.mirai.contact.Member;
+import net.mamoe.mirai.contact.QQ;
+
+/**
+ * 构造阻塞式的联系人.
+ */
+public final class BlockingContacts {
+    public static BlockingQQ createBlocking(QQ qq) {
+        return new BlockingQQImpl(qq);
+    }
+
+    public static BlockingGroup createBlocking(Group group) {
+        return new BlockingGroupImpl(group);
+    }
+
+    public static BlockingMember createBlocking(Member member) {
+        return new BlockingMemberImpl(member);
+    }
+}
diff --git a/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingGroup.java b/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingGroup.java
new file mode 100644
index 000000000..37fdc62b1
--- /dev/null
+++ b/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingGroup.java
@@ -0,0 +1,57 @@
+package net.mamoe.mirai.japt;
+
+import net.mamoe.mirai.network.protocol.tim.packet.action.GroupInfo;
+
+import java.util.Map;
+
+@SuppressWarnings("unused")
+public interface BlockingGroup extends BlockingContact {
+    /**
+     * 内部 ID. 内部 ID 为 [GroupId] 的映射
+     */
+    Long getInternalId();
+
+    /**
+     * 群主 (同步事件更新)
+     * 进行 [updateGroupInfo] 时将会更新这个值.
+     */
+    BlockingMember getOwner();
+
+    /**
+     * 群名称 (同步事件更新)
+     * 进行 [updateGroupInfo] 时将会更新这个值.
+     */
+    String getName();
+
+    /**
+     * 入群公告, 没有时为空字符串. (同步事件更新)
+     * 进行 [updateGroupInfo] 时将会更新这个值.
+     */
+    String getAnnouncement();
+
+    /**
+     * 在 [Group] 实例创建的时候查询一次. 并与事件同步事件更新
+     * <p>
+     * **注意**: 获得的列表仅为这一时刻的成员列表的镜像. 它将不会被更新
+     */
+    Map<Long, BlockingMember> getMembers();
+
+    /**
+     * 获取群成员. 若此 ID 的成员不存在, 则会抛出 [kotlin.NoSuchElementException]
+     */
+    BlockingMember getMember(long id);
+
+    /**
+     * 更新群资料. 群资料会与服务器事件同步事件更新, 一般情况下不需要手动更新.
+     *
+     * @return 这一时刻的群资料
+     */
+    GroupInfo updateGroupInfo();
+
+    /**
+     * 让机器人退出这个群. 机器人必须为非群主才能退出. 否则将会失败
+     */
+    boolean quit();
+
+    String toFullString();
+}
\ No newline at end of file
diff --git a/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingMember.java b/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingMember.java
new file mode 100644
index 000000000..bea594a00
--- /dev/null
+++ b/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingMember.java
@@ -0,0 +1,29 @@
+package net.mamoe.mirai.japt;
+
+import net.mamoe.mirai.contact.MemberPermission;
+
+@SuppressWarnings("unused")
+public interface BlockingMember {
+    /**
+     * 所在的群
+     */
+    BlockingGroup getGroup();
+
+    /**
+     * 权限
+     */
+    MemberPermission getPermission();
+
+    /**
+     * 禁言
+     *
+     * @param durationSeconds 持续时间. 精确到秒. 范围区间表示为 `(0s, 30days]`. 超过范围则会抛出异常.
+     * @return 若机器人无权限禁言这个群成员, 返回 `false`
+     */
+    Boolean mute(int durationSeconds);
+
+    /**
+     * 解除禁言
+     */
+    void unmute();
+}
diff --git a/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingQQ.java b/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingQQ.java
new file mode 100644
index 000000000..e01aa246c
--- /dev/null
+++ b/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingQQ.java
@@ -0,0 +1,31 @@
+package net.mamoe.mirai.japt;
+
+import net.mamoe.mirai.contact.data.Profile;
+import net.mamoe.mirai.network.protocol.tim.packet.action.FriendNameRemark;
+import net.mamoe.mirai.network.protocol.tim.packet.action.PreviousNameList;
+import org.jetbrains.annotations.NotNull;
+
+@SuppressWarnings("unused")
+public interface BlockingQQ extends BlockingContact {
+    /**
+     * 查询用户资料
+     */
+    @NotNull
+    Profile queryProfile();
+
+    /**
+     * 查询曾用名.
+     * <p>
+     * 曾用名可能是:
+     * - 昵称
+     * - 共同群内的群名片
+     */
+    @NotNull
+    PreviousNameList queryPreviousNameList();
+
+    /**
+     * 查询机器人账号给这个人设置的备注
+     */
+    @NotNull
+    FriendNameRemark queryRemark();
+}
diff --git a/mirai-japt/src/main/kotlin/net/mamoe/mirai/japt/BlockingContacts.kt b/mirai-japt/src/main/kotlin/net/mamoe/mirai/japt/BlockingContacts.kt
new file mode 100644
index 000000000..e583d33b5
--- /dev/null
+++ b/mirai-japt/src/main/kotlin/net/mamoe/mirai/japt/BlockingContacts.kt
@@ -0,0 +1,11 @@
+@file:Suppress("NOTHING_TO_INLINE", "unused")
+
+package net.mamoe.mirai.japt
+
+import net.mamoe.mirai.contact.Group
+import net.mamoe.mirai.contact.Member
+import net.mamoe.mirai.contact.QQ
+
+inline fun Group.blocking(): BlockingGroup = BlockingContacts.createBlocking(this)
+inline fun QQ.blocking(): BlockingQQ = BlockingContacts.createBlocking(this)
+inline fun Member.blocking(): BlockingMember = BlockingContacts.createBlocking(this)
\ No newline at end of file
diff --git a/mirai-japt/src/main/kotlin/net/mamoe/mirai/japt/BlockingContactsImpl.kt b/mirai-japt/src/main/kotlin/net/mamoe/mirai/japt/BlockingContactsImpl.kt
new file mode 100644
index 000000000..550d5a807
--- /dev/null
+++ b/mirai-japt/src/main/kotlin/net/mamoe/mirai/japt/BlockingContactsImpl.kt
@@ -0,0 +1,46 @@
+@file:Suppress("EXPERIMENTAL_API_USAGE")
+
+package net.mamoe.mirai.japt
+
+import kotlinx.coroutines.runBlocking
+import net.mamoe.mirai.Bot
+import net.mamoe.mirai.contact.Group
+import net.mamoe.mirai.contact.Member
+import net.mamoe.mirai.contact.MemberPermission
+import net.mamoe.mirai.contact.QQ
+import net.mamoe.mirai.contact.data.Profile
+import net.mamoe.mirai.message.MessageChain
+import net.mamoe.mirai.network.protocol.tim.packet.action.FriendNameRemark
+import net.mamoe.mirai.network.protocol.tim.packet.action.GroupInfo
+import net.mamoe.mirai.network.protocol.tim.packet.action.PreviousNameList
+
+internal class BlockingQQImpl(private val delegate: QQ) : BlockingQQ {
+    override fun getBot(): Bot = delegate.bot
+    override fun getId(): Long = delegate.id.toLong()
+    override fun sendMessage(messages: MessageChain) = runBlocking { delegate.sendMessage(messages) }
+    override fun queryProfile(): Profile = runBlocking { delegate.queryProfile() }
+    override fun queryPreviousNameList(): PreviousNameList = runBlocking { delegate.queryPreviousNameList() }
+    override fun queryRemark(): FriendNameRemark = runBlocking { delegate.queryRemark() }
+}
+
+internal class BlockingGroupImpl(private val delegate: Group) : BlockingGroup {
+    override fun sendMessage(messages: MessageChain) = runBlocking { delegate.sendMessage(messages) }
+    override fun getOwner(): BlockingMember = delegate.owner.blocking()
+    override fun getName(): String = delegate.name
+    override fun getId(): Long = delegate.id.toLong()
+    override fun updateGroupInfo(): GroupInfo = runBlocking { delegate.updateGroupInfo() }
+    override fun toFullString(): String = delegate.toFullString()
+    override fun getMember(id: Long): BlockingMember = delegate.getMember(id.toUInt()).blocking()
+    override fun getBot(): Bot = delegate.bot
+    override fun getAnnouncement(): String = delegate.announcement
+    override fun getMembers(): MutableMap<Long, BlockingMember> = delegate.members.mapKeys { it.key.toLong() }.mapValues { it.value.blocking() }.toMutableMap()
+    override fun getInternalId(): Long = delegate.internalId.value.toLong()
+    override fun quit(): Boolean = runBlocking { delegate.quit().isSuccess }
+}
+
+internal class BlockingMemberImpl(private val delegate: Member) : BlockingMember {
+    override fun getGroup(): BlockingGroup = delegate.group.blocking()
+    override fun getPermission(): MemberPermission = delegate.permission
+    override fun mute(durationSeconds: Int): Boolean = runBlocking { delegate.mute(durationSeconds) }
+    override fun unmute() = runBlocking { delegate.unmute() }
+}
\ No newline at end of file
diff --git a/mirai-japt/src/test/kotlin/BlockingTest.java b/mirai-japt/src/test/kotlin/BlockingTest.java
new file mode 100644
index 000000000..385ab7c23
--- /dev/null
+++ b/mirai-japt/src/test/kotlin/BlockingTest.java
@@ -0,0 +1,14 @@
+public class BlockingTest {
+
+
+    public static void main(String[] args) {
+        //Bot bot = new Bot(new BotAccount(123456, ""), EmptyCoroutineContext.INSTANCE);
+        //if (bot.getNetwork().login() != LoginResult.) {
+        //    throw IllegalStateException("Login failed")
+        //}
+        //bot.getContacts().getGroups();
+
+        //var qq = BlockingContacts.createBlocking(bot.getContacts().getQQ(123L))
+        //println(createBlocking.queryRemark())
+    }
+}
diff --git a/mirai-japt/src/test/kotlin/TestBlocking.kt b/mirai-japt/src/test/kotlin/TestBlocking.kt
new file mode 100644
index 000000000..01393988a
--- /dev/null
+++ b/mirai-japt/src/test/kotlin/TestBlocking.kt
@@ -0,0 +1,31 @@
+import net.mamoe.mirai.Bot
+import net.mamoe.mirai.BotAccount
+import net.mamoe.mirai.japt.BlockingContacts
+import net.mamoe.mirai.network.protocol.tim.packet.login.LoginResult
+import java.io.File
+
+@ExperimentalUnsignedTypes
+private fun readTestAccount(): BotAccount? {
+    val file = File("testAccount.txt")
+    if (!file.exists() || !file.canRead()) {
+        return null
+    }
+
+    val lines = file.readLines()
+    return try {
+        BotAccount(lines[0].toUInt(), lines[1])
+    } catch (e: Exception) {
+        null
+    }
+}
+
+@ExperimentalUnsignedTypes
+suspend fun main() {
+    val bot = Bot(readTestAccount()!!)
+    if (bot.network.login() != LoginResult.SUCCESS) {
+        throw IllegalStateException("Login failed")
+    }
+
+    val createBlocking = BlockingContacts.createBlocking(bot.contacts.getQQ(123L))
+    println(createBlocking.queryRemark())
+}
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
index 4deff9a2c..1280fff2e 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -36,6 +36,7 @@ include(':mirai-core')
 include(':mirai-core-timpc')
 include(':mirai-core-qqandroid')
 
+include(':mirai-japt')
 include(':mirai-console')
 //include(':mirai-api')
 include(':mirai-api-http')