refact: merge client into toolchains
diff --git a/hugegraph-client/.github/ISSUE_TEMPLATE/bug_report.yml b/hugegraph-client/.github/ISSUE_TEMPLATE/bug_report.yml
new file mode 100644
index 0000000..c5645d8
--- /dev/null
+++ b/hugegraph-client/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -0,0 +1,100 @@
+name: Bug report (反馈 Bug)
+description: Create a bug report to help HugeGraph improve
+title: '[Bug] describe the main problem'
+labels:
+  - bug
+
+body:
+  - type: markdown
+    attributes:
+      value: >-
+        ### Note (特别注意) : 
+
+        > 1. 请先**搜索**现有的[Server-Issues](https://github.com/hugegraph/hugegraph/issues) 与
+        [Client-Issues](https://github.com/hugegraph/hugegraph-client/issues) 中没有与您相同
+        / 相关的问题 (请勿重复提交)
+
+        > 2. 我们需要尽可能**详细**的信息来**复现**问题, 越详细的信息 (包括**日志 / 截图 / 配置**等)
+        会**越快**被响应和处理
+
+        > 3. Issue 标题请保持原有模板分类(例如:`[Bug]`), 长段描述之间可以增加`空行`或使用`序号`标记, 保持排版清晰
+
+        > 4. 请在对应的模块提交 issue, 缺乏有效信息 / 长时间 (> 14 天) 没有回复的 issue 可能会被 **关闭**
+        (更新时会再开启)
+  
+  - type: dropdown
+    attributes:
+      label: Bug Type (问题类型)
+      options:
+        - gremlin (结果不合预期)
+        - rest-api (结果不合预期)
+        - other exception / error (其他异常报错)
+        - server status (连接异常)
+        - logic (逻辑设计问题)
+        - performence (性能下降)
+        - others (please edit later)
+  
+  - type: checkboxes
+    attributes:
+      label: Before submit
+      options:
+        - label: 我已经确认现有的 [Server-Issues](https://github.com/hugegraph/hugegraph/issues) 与 [Client-Issues](https://github.com/hugegraph/hugegraph-client/issues) 中没有相同 / 重复问题
+          required: true
+
+  - type: textarea
+    attributes:
+      label: Environment (环境信息)
+      description: |
+        > server version could get from [rest-api](https://hugegraph.github.io/hugegraph-doc/clients/restful-api/other.html) (http://localhost:8080/versions)
+      value: |
+        - Server Version: v0.11.x
+        - Client Version: v1.x
+        - Data Size:  xx vertices, xx edges <!-- (like 1000W 点, 9000W 边) -->
+    validations:
+      required: true
+  
+  - type: textarea
+    attributes:
+      label: Expected & Actual behavior (期望与实际表现)
+      description: |
+        > we can refer [How to create a minimal reproducible Example](https://stackoverflow.com/help/minimal-reproducible-example) (如何提供最简的可复现用例)
+        > if possible, please provide screenshots or GIF (请提供清晰的截图, 动图录屏更佳)
+      placeholder: |        
+        type the main problem here 
+        
+        ```java
+        // Exception / Error info (尽可能详细的日志 + 完整异常栈)
+
+        ```
+    validations:
+      required: true
+  
+  - type: textarea
+    attributes:
+      label: Vertex/Edge example (问题点 / 边数据举例)
+      description: |
+        > 如果问题与具体的点 / 边数据相关, 请提供完整的`查询语句 + 返回 JSON 结果`
+      placeholder: |
+        // Query URL
+        GET http://localhost:8080/gremlin?gremlin=hugegraph.traversal().V('1:tom')
+
+        // JSON of Vertex / Edge
+        {
+          "vertex": { "id": "xxx" }
+        }
+      render: javascript
+  
+  - type: textarea
+    attributes:
+      label: Schema [VertexLabel, EdgeLabel, IndexLabel] (元数据结构)
+      description: |
+        > 如果问题与具体的点类型 / 边类型 / 索引类型相关, 请提供完整的 `Schema 返回 JSON 结果`
+      placeholder: |
+        // Query URL
+        GET http://localhost:8080/graphs/hugegraph/schema/vertexlabels
+
+        // JSON of GraphSchema
+        {
+          "vertex": { "id": "xxx" }
+        }
+      render: javascript
diff --git a/hugegraph-client/.github/ISSUE_TEMPLATE/config.yml b/hugegraph-client/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 0000000..7aa5dc1
--- /dev/null
+++ b/hugegraph-client/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,10 @@
+blank_issues_enabled: false
+
+# 设置提 issue 前的参考文档
+contact_links:
+  - name: HugeGraph Client Doc
+    url: https://hugegraph.github.io/hugegraph-doc/quickstart/hugegraph-client.html
+    about: Please search question here before opening a new issue
+  - name: HugeGraph API Doc
+    url: https://hugegraph.github.io/hugegraph-doc/clients/hugegraph-api.html
+    about: Please search usage here before opening a new issue
diff --git a/hugegraph-client/.github/ISSUE_TEMPLATE/feature_request.yml b/hugegraph-client/.github/ISSUE_TEMPLATE/feature_request.yml
new file mode 100644
index 0000000..4012606
--- /dev/null
+++ b/hugegraph-client/.github/ISSUE_TEMPLATE/feature_request.yml
@@ -0,0 +1,15 @@
+name: Feature request (新需求 / 功能)
+description: Give an idea for HugeGraph
+title: '[Feature] describe the new feature'
+labels:
+  - feature
+
+body:
+  - type: textarea
+    attributes:
+      label: Feature Description (功能描述)
+      description: |
+        > 请简要描述新功能 / 需求的使用场景或上下文, 最好能给个具体的例子说明
+      placeholder: type the feature description here 
+    validations:
+      required: true
diff --git a/hugegraph-client/.github/ISSUE_TEMPLATE/question_ask.yml b/hugegraph-client/.github/ISSUE_TEMPLATE/question_ask.yml
new file mode 100644
index 0000000..16811ae
--- /dev/null
+++ b/hugegraph-client/.github/ISSUE_TEMPLATE/question_ask.yml
@@ -0,0 +1,99 @@
+name: Ask question (提问)
+description: Question about usage or configs in HugeGraph
+title: '[Question] describe your problem'
+
+body:
+  - type: markdown
+    attributes:
+      value: >-
+        ### Note (特别注意) : 
+
+        > 1. 请先**搜索**现有的[Server-Issues](https://github.com/hugegraph/hugegraph/issues) 与
+        [Client-Issues](https://github.com/hugegraph/hugegraph-client/issues) 中没有与您相同
+        / 相关的问题 (请勿重复提交)
+
+        > 2. 我们需要尽可能**详细**的信息来**复现**问题, 越详细的信息 (包括**日志 / 截图 / 配置**等)
+        会**越快**被响应和处理
+
+        > 3. Issue 标题请保持原有模板分类(例如:`[Bug]`), 长段描述之间可以增加`空行`或使用`序号`标记, 保持排版清晰
+
+        > 4. 请在对应的模块提交 issue, 缺乏有效信息 / 长时间 (> 14 天) 没有回复的 issue 可能会被 **关闭**
+        (更新时会再开启)
+  
+  - type: dropdown
+    attributes:
+      label: Problem Type (问题类型)
+      options:
+        - gremlin (结果不合预期)
+        - rest-api (结果不合预期)
+        - server status (连接异常)
+        - configs (配置项 / 文档相关)
+        - struct / logic (架构 / 逻辑设计问题)
+        - performence (性能优化)
+        - other exception / error (其他异常报错)
+        - others (please edit later)
+  
+  - type: checkboxes
+    attributes:
+      label: Before submit
+      options:
+        - label: 我已经确认现有的 [Server-Issues](https://github.com/hugegraph/hugegraph/issues) 与 [Client-Issues](https://github.com/hugegraph/hugegraph-client/issues) 中没有相同 / 重复问题
+          required: true
+
+  - type: textarea
+    attributes:
+      label: Environment (环境信息)
+      description: |
+        > server version could get from [rest-api](https://hugegraph.github.io/hugegraph-doc/clients/restful-api/other.html) (http://localhost:8080/versions)
+      value: |
+        - Server Version: v0.11.x
+        - Client Version: v1.x
+        - Data Size:  xx vertices, xx edges <!-- (like 1000W 点, 9000W 边) -->
+    validations:
+      required: true
+  
+  - type: textarea
+    attributes:
+      label: Your Question (问题描述)
+      description: |
+        > 图使用 / 配置相关问题,请优先参考 [REST-API 文档](https://hugegraph.github.io/hugegraph-doc/clients/hugegraph-api.html), 以及 [Server 配置文档](https://hugegraph.github.io/hugegraph-doc/config/config-option.html)
+        > if possible, please provide screenshots or GIF (请提供清晰的截图, 动图录屏更佳)
+      placeholder: |        
+        type the main problem here 
+        
+        ```java
+        // Exception / Error info (尽可能详细的日志 + 完整异常栈)
+
+        ```
+    validations:
+      required: true
+  
+  - type: textarea
+    attributes:
+      label: Vertex/Edge example (问题点 / 边数据举例)
+      description: |
+        > 如果问题与具体的点 / 边数据相关, 请提供完整的`查询语句 + 返回 JSON 结果`
+      placeholder: |
+        // Query URL
+        GET http://localhost:8080/gremlin?gremlin=hugegraph.traversal().V('1:tom')
+
+        // JSON of Vertex / Edge
+        {
+          "vertex": { "id": "xxx" }
+        }
+      render: javascript
+  
+  - type: textarea
+    attributes:
+      label: Schema [VertexLabel, EdgeLabel, IndexLabel] (元数据结构)
+      description: |
+        > 如果问题与具体的点类型 / 边类型 / 索引类型相关, 请提供完整的 `Schema 返回 JSON 结果`
+      placeholder: |
+        // Query URL
+        GET http://localhost:8080/graphs/hugegraph/schema/vertexlabels
+
+        // JSON of GraphSchema
+        {
+          "vertex": { "id": "xxx" }
+        }
+      render: javascript
diff --git a/hugegraph-client/.github/workflows/ci.yml b/hugegraph-client/.github/workflows/ci.yml
new file mode 100644
index 0000000..2be805f
--- /dev/null
+++ b/hugegraph-client/.github/workflows/ci.yml
@@ -0,0 +1,61 @@
+name: hugegraph-client ci
+
+on:
+  push:
+    branches:
+      - master
+      - /^release-.*$/
+      - /^test-.*$/
+  pull_request:
+    branches:
+      - master
+      - /^release-.*$/
+      - /^test-.*$/
+
+jobs:
+  build:
+    runs-on: ubuntu-20.04
+    env:
+      TRAVIS_DIR: assembly/travis
+      COMMIT_ID: 1d031c5905cbef008dd5fb468576b0e6a9445181
+    strategy:
+      fail-fast: false
+      matrix:
+        JAVA_VERSION: ['8']
+    steps:
+      - name: Install JDK 8
+        uses: actions/setup-java@v2
+        with:
+          java-version: ${{ matrix.JAVA_VERSION }}
+          distribution: 'zulu'
+
+      - name: Cache Maven packages
+        uses: actions/cache@v2
+        with:
+          path: ~/.m2
+          key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
+          restore-keys: ${{ runner.os }}-m2
+
+      - name: Checkout
+        uses: actions/checkout@v2
+        with:
+          fetch-depth: 2
+
+      - name: Compile
+        run: |
+          mvn compile -Dmaven.javadoc.skip=true | grep -v "Downloading\|Downloaded"
+
+      - name: Prepare env and service
+        run: |
+          $TRAVIS_DIR/install-hugegraph-from-source.sh $COMMIT_ID
+
+      - name: Run test
+        run: |
+          mvn test -Dtest=UnitTestSuite
+          mvn test -Dtest=ApiTestSuite
+          mvn test -Dtest=FuncTestSuite
+
+      - name: Upload coverage to Codecov
+        uses: codecov/codecov-action@v1
+        with:
+          file: target/jacoco.xml
diff --git a/hugegraph-client/.github/workflows/release.yml b/hugegraph-client/.github/workflows/release.yml
new file mode 100644
index 0000000..b0c20d2
--- /dev/null
+++ b/hugegraph-client/.github/workflows/release.yml
@@ -0,0 +1,39 @@
+name: release maven package
+
+on:
+  release:
+    types: [ published ]
+
+jobs:
+  build:
+    runs-on: ubuntu-20.04
+    steps:
+      - name: Install JDK 8
+        uses: actions/setup-java@v2
+        with:
+          java-version: '8'
+          distribution: 'zulu'
+
+      - name: Cache Maven packages
+        uses: actions/cache@v2
+        with:
+          path: ~/.m2
+          key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
+          restore-keys: ${{ runner.os }}-m2
+
+      - name: Checkout
+        uses: actions/checkout@v2
+        with:
+          fetch-depth: 2
+
+      - name: Release Maven package
+        uses: samuelmeuli/action-maven-publish@v1
+        with:
+          gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
+          gpg_passphrase: ${{ secrets.GPG_PASSPHRASE }}
+          nexus_username: ${{ secrets.NEXUS_USERNAME }}
+          nexus_password: ${{ secrets.NEXUS_PASSWORD }}
+          server_id: sonatype-nexus-staging
+          maven_profiles: "release"
+          maven_args: >
+            -Dmaven.test.skip=true
diff --git a/hugegraph-client/.github/workflows/stale.yml b/hugegraph-client/.github/workflows/stale.yml
new file mode 100644
index 0000000..1813bd3
--- /dev/null
+++ b/hugegraph-client/.github/workflows/stale.yml
@@ -0,0 +1,36 @@
+name: Mark stale issues and pull requests
+
+on:
+  schedule:
+  - cron: "0 21 * * *"
+
+jobs:
+  stale:
+
+    runs-on: ubuntu-latest
+    permissions:
+      issues: write
+      pull-requests: write
+
+    steps:
+    - uses: actions/stale@v3
+      with:
+        repo-token: ${{ secrets.GITHUB_TOKEN }}
+        stale-issue-message: 'Due to the lack of activity, the current issue is marked as stale and will be closed after 20 days, any update will remove the stale label'
+        stale-pr-message: 'Due to the lack of activity, the current pr is marked as stale and will be closed after 180 days, any update will remove the stale label'
+        stale-issue-label: 'inactive'
+        stale-pr-label: 'inactive'
+        exempt-issue-labels: 'feature,bug,enhancement,improvement,wontfix,todo'
+
+        days-before-issue-stale: 15
+        days-before-issue-close: 20
+        days-before-pr-stale: 30
+        days-before-pr-close: 180
+        operations-per-run: 10
+        start-date: '2018-12-01T00:00:00Z'
+
+        exempt-all-assignees: true
+        remove-stale-when-updated: true
+        exempt-all-pr-milestones: true
+        delete-branch: false
+        enable-statistics: true
diff --git a/hugegraph-client/.travis.yml b/hugegraph-client/.travis.yml
new file mode 100644
index 0000000..7babb8d
--- /dev/null
+++ b/hugegraph-client/.travis.yml
@@ -0,0 +1,30 @@
+language: java
+
+jdk:
+  - openjdk8
+
+sudo: required
+
+branches:
+  only:
+  - master
+  - /^release-.*$/
+  - /^test-.*$/
+
+install: mvn compile -Dmaven.javadoc.skip=true | grep -v "Downloading\|Downloaded"
+
+before_script:
+  - $TRAVIS_DIR/install-hugegraph-from-source.sh $TRAVIS_BRANCH | grep -v "Downloading\|Downloaded"
+
+script:
+  - mvn test -Dtest=UnitTestSuite
+  - mvn test -Dtest=ApiTestSuite
+  - mvn test -Dtest=FuncTestSuite
+
+after_success:
+  - bash <(curl -s https://codecov.io/bash)
+
+env:
+  global:
+  - TRAVIS_DIR=assembly/travis
+  - COMMIT_ID=461948ee262cc696853a8c8ba1306e6b371e3e89
diff --git a/hugegraph-client/README.md b/hugegraph-client/README.md
new file mode 100644
index 0000000..21a752a
--- /dev/null
+++ b/hugegraph-client/README.md
@@ -0,0 +1,19 @@
+# hugegraph-client
+
+[![License](https://img.shields.io/badge/license-Apache%202-0E78BA.svg)](https://www.apache.org/licenses/LICENSE-2.0.html)
+[![Build Status](https://travis-ci.org/hugegraph/hugegraph-client.svg?branch=master)](https://travis-ci.org/hugegraph/hugegraph-client)
+[![codecov](https://codecov.io/gh/hugegraph/hugegraph-client/branch/master/graph/badge.svg)](https://codecov.io/gh/hugegraph/hugegraph-client)
+[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.baidu.hugegraph/hugegraph-client/badge.svg)](https://mvnrepository.com/artifact/com.baidu.hugegraph/hugegraph-client)
+
+hugegraph-client is a Java-written client of [HugeGraph](https://github.com/hugegraph/hugegraph), providing operations of graph, schema, gremlin, variables and traversals etc. All these operations are interpreted and translated into RESTful requests to HugeGraph Server. Besides, hugegraph-client also checks arguments, serializes and deserializes structures and encapsulates server exceptions.
+
+## Features
+
+- Graph Operation, CRUD of vertexes and edges, batch load of vertexes and edges
+- Schema Operation, CRUD of vertex label, edge label, index label and property key
+- Gremlin Traversal Statements
+- RESTful Traversals, shortest path, k-out, k-neighbor, paths and crosspoints etc.
+- Variables, CRUD of variables
+
+## Licence
+The same as HugeGraph, hugegraph-client is also licensed under Apache 2.0 License.
diff --git a/hugegraph-client/assembly/travis/codecov.yml b/hugegraph-client/assembly/travis/codecov.yml
new file mode 100644
index 0000000..8a2a5bb
--- /dev/null
+++ b/hugegraph-client/assembly/travis/codecov.yml
@@ -0,0 +1,2 @@
+ignore:
+  - "src/main/java/com/baidu/hugegraph/example"
diff --git a/hugegraph-client/assembly/travis/conf/graphs/hugegraph.properties b/hugegraph-client/assembly/travis/conf/graphs/hugegraph.properties
new file mode 100644
index 0000000..9c683e9
--- /dev/null
+++ b/hugegraph-client/assembly/travis/conf/graphs/hugegraph.properties
@@ -0,0 +1,61 @@
+# gremlin entrence to create graph
+gremlin.graph=com.baidu.hugegraph.auth.HugeFactoryAuthProxy
+
+# cache config
+#schema.cache_capacity=100000
+# vertex-cache default is 1000w, 10min expired
+#vertex.cache_capacity=10000000
+#vertex.cache_expire=600
+# edge-cache default is 100w, 10min expired
+#edge.cache_capacity=1000000
+#edge.cache_expire=600
+
+
+# schema illegal name template
+#schema.illegal_name_regex=\s+|~.*
+
+#vertex.default_label=vertex
+
+backend=rocksdb
+serializer=binary
+
+store=hugegraph
+
+search.text_analyzer=jieba
+search.text_analyzer_mode=INDEX
+
+# rocksdb backend config
+#rocksdb.data_path=/path/to/disk
+#rocksdb.wal_path=/path/to/disk
+
+
+# cassandra backend config
+cassandra.host=localhost
+cassandra.port=9042
+cassandra.username=
+cassandra.password=
+#cassandra.connect_timeout=5
+#cassandra.read_timeout=20
+#cassandra.keyspace.strategy=SimpleStrategy
+#cassandra.keyspace.replication=3
+
+# hbase backend config
+#hbase.hosts=localhost
+#hbase.port=2181
+#hbase.znode_parent=/hbsae
+#hbase.threads_max=64
+
+# mysql backend config
+#jdbc.driver=com.mysql.jdbc.Driver
+#jdbc.url=jdbc:mysql://127.0.0.1:3306
+#jdbc.username=root
+#jdbc.password=
+#jdbc.reconnect_max_times=3
+#jdbc.reconnect_interval=3
+#jdbc.sslmode=false
+
+# palo backend config
+#palo.host=127.0.0.1
+#palo.poll_interval=10
+#palo.temp_dir=./palo-data
+#palo.file_limit_size=32
diff --git a/hugegraph-client/assembly/travis/conf/gremlin-server.yaml b/hugegraph-client/assembly/travis/conf/gremlin-server.yaml
new file mode 100644
index 0000000..69e8990
--- /dev/null
+++ b/hugegraph-client/assembly/travis/conf/gremlin-server.yaml
@@ -0,0 +1,104 @@
+# host and port of gremlin server, need to be consistent with host and port in rest-server.properties
+#host: 127.0.0.1
+#port: 8182
+
+# timeout in ms of gremlin query
+scriptEvaluationTimeout: 30000
+
+channelizer: org.apache.tinkerpop.gremlin.server.channel.WsAndHttpChannelizer
+# don't set graph at here, this happens after support for dynamically adding graph
+graphs: {
+}
+scriptEngines: {
+  gremlin-groovy: {
+    plugins: {
+      com.baidu.hugegraph.plugin.HugeGraphGremlinPlugin: {},
+      org.apache.tinkerpop.gremlin.server.jsr223.GremlinServerGremlinPlugin: {},
+      org.apache.tinkerpop.gremlin.jsr223.ImportGremlinPlugin: {
+        classImports: [
+          java.lang.Math,
+          com.baidu.hugegraph.backend.id.IdGenerator,
+          com.baidu.hugegraph.type.define.Directions,
+          com.baidu.hugegraph.type.define.NodeRole,
+          com.baidu.hugegraph.traversal.algorithm.CollectionPathsTraverser,
+          com.baidu.hugegraph.traversal.algorithm.CountTraverser,
+          com.baidu.hugegraph.traversal.algorithm.CustomizedCrosspointsTraverser,
+          com.baidu.hugegraph.traversal.algorithm.CustomizePathsTraverser,
+          com.baidu.hugegraph.traversal.algorithm.FusiformSimilarityTraverser,
+          com.baidu.hugegraph.traversal.algorithm.HugeTraverser,
+          com.baidu.hugegraph.traversal.algorithm.JaccardSimilarTraverser,
+          com.baidu.hugegraph.traversal.algorithm.KneighborTraverser,
+          com.baidu.hugegraph.traversal.algorithm.KoutTraverser,
+          com.baidu.hugegraph.traversal.algorithm.MultiNodeShortestPathTraverser,
+          com.baidu.hugegraph.traversal.algorithm.NeighborRankTraverser,
+          com.baidu.hugegraph.traversal.algorithm.PathsTraverser,
+          com.baidu.hugegraph.traversal.algorithm.PersonalRankTraverser,
+          com.baidu.hugegraph.traversal.algorithm.SameNeighborTraverser,
+          com.baidu.hugegraph.traversal.algorithm.ShortestPathTraverser,
+          com.baidu.hugegraph.traversal.algorithm.SingleSourceShortestPathTraverser,
+          com.baidu.hugegraph.traversal.algorithm.SubGraphTraverser,
+          com.baidu.hugegraph.traversal.algorithm.TemplatePathsTraverser,
+          com.baidu.hugegraph.traversal.algorithm.steps.EdgeStep,
+          com.baidu.hugegraph.traversal.algorithm.steps.RepeatEdgeStep,
+          com.baidu.hugegraph.traversal.algorithm.steps.WeightedEdgeStep,
+          com.baidu.hugegraph.traversal.optimize.Text,
+          com.baidu.hugegraph.traversal.optimize.TraversalUtil,
+          com.baidu.hugegraph.util.DateUtil
+        ],
+        methodImports: [java.lang.Math#*]
+      },
+      org.apache.tinkerpop.gremlin.jsr223.ScriptFileGremlinPlugin: {
+        files: [scripts/empty-sample.groovy]
+      }
+    }
+  }
+}
+serializers:
+  - { className: org.apache.tinkerpop.gremlin.driver.ser.GraphBinaryMessageSerializerV1,
+      config: {
+        serializeResultToString: false,
+        ioRegistries: [com.baidu.hugegraph.io.HugeGraphIoRegistry]
+      }
+  }
+  - { className: org.apache.tinkerpop.gremlin.driver.ser.GraphSONMessageSerializerV1d0,
+      config: {
+        serializeResultToString: false,
+        ioRegistries: [com.baidu.hugegraph.io.HugeGraphIoRegistry]
+      }
+  }
+  - { className: org.apache.tinkerpop.gremlin.driver.ser.GraphSONMessageSerializerV2d0,
+      config: {
+        serializeResultToString: false,
+        ioRegistries: [com.baidu.hugegraph.io.HugeGraphIoRegistry]
+      }
+  }
+  - { className: org.apache.tinkerpop.gremlin.driver.ser.GraphSONMessageSerializerV3d0,
+      config: {
+        serializeResultToString: false,
+        ioRegistries: [com.baidu.hugegraph.io.HugeGraphIoRegistry]
+      }
+  }
+metrics: {
+  consoleReporter: {enabled: false, interval: 180000},
+  csvReporter: {enabled: false, interval: 180000, fileName: ./metrics/gremlin-server-metrics.csv},
+  jmxReporter: {enabled: false},
+  slf4jReporter: {enabled: false, interval: 180000},
+  gangliaReporter: {enabled: false, interval: 180000, addressingMode: MULTICAST},
+  graphiteReporter: {enabled: false, interval: 180000}
+}
+maxInitialLineLength: 4096
+maxHeaderSize: 8192
+maxChunkSize: 8192
+maxContentLength: 65536
+maxAccumulationBufferComponents: 1024
+resultIterationBatchSize: 64
+writeBufferLowWaterMark: 32768
+writeBufferHighWaterMark: 65536
+ssl: {
+  enabled: false
+}
+authentication: {
+  authenticator: com.baidu.hugegraph.auth.StandardAuthenticator,
+  authenticationHandler: com.baidu.hugegraph.auth.WsAndHttpBasicAuthHandler,
+  config: {tokens: conf/rest-server.properties}
+}
diff --git a/hugegraph-client/assembly/travis/conf/rest-server.properties b/hugegraph-client/assembly/travis/conf/rest-server.properties
new file mode 100644
index 0000000..d44ff6b
--- /dev/null
+++ b/hugegraph-client/assembly/travis/conf/rest-server.properties
@@ -0,0 +1,41 @@
+# bind url
+restserver.url=http://127.0.0.1:8080
+# gremlin server url, need to be consistent with host and port in gremlin-server.yaml
+#gremlinserver.url=http://127.0.0.1:8182
+
+graphs=./conf/graphs
+
+# The maximum thread ratio for batch writing, only take effect if the batch.max_write_threads is 0
+batch.max_write_ratio=80
+batch.max_write_threads=0
+
+# authentication configs
+# choose 'com.baidu.hugegraph.auth.StandardAuthenticator' or 'com.baidu.hugegraph.auth.ConfigAuthenticator'
+auth.authenticator=com.baidu.hugegraph.auth.StandardAuthenticator
+
+# for StandardAuthenticator mode
+#auth.graph_store=hugegraph
+# auth client config
+#auth.remote_url=127.0.0.1:8899,127.0.0.1:8898,127.0.0.1:8897
+
+# for ConfigAuthenticator mode
+#auth.admin_token=
+#auth.user_tokens=[]
+
+# rpc group configs of multi graph servers
+# rpc server configs
+rpc.server_host=127.0.0.1
+rpc.server_port=8090
+#rpc.server_timeout=30
+
+# rpc client configs (like enable to keep cache consistency)
+rpc.remote_url=127.0.0.1:8090
+#rpc.client_connect_timeout=20
+#rpc.client_reconnect_period=10
+#rpc.client_read_timeout=40
+#rpc.client_retries=3
+#rpc.client_load_balancer=consistentHash
+
+# lightweight load balancing (beta)
+server.id=server-1
+server.role=master
diff --git a/hugegraph-client/assembly/travis/install-hugegraph-from-source.sh b/hugegraph-client/assembly/travis/install-hugegraph-from-source.sh
new file mode 100755
index 0000000..c594320
--- /dev/null
+++ b/hugegraph-client/assembly/travis/install-hugegraph-from-source.sh
@@ -0,0 +1,49 @@
+#!/bin/bash
+
+set -ev
+
+if [[ $# -ne 1 ]]; then
+    echo "Must pass commit id of hugegraph repo"
+    exit 1
+fi
+
+COMMIT_ID=$1
+HUGEGRAPH_GIT_URL="https://github.com/hugegraph/hugegraph.git"
+GIT_DIR=hugegraph
+
+# download code and compile
+git clone --depth 100 ${HUGEGRAPH_GIT_URL}
+cd "${GIT_DIR}"
+git checkout ${COMMIT_ID}
+mvn package -DskipTests
+
+TAR=$(echo hugegraph-*.tar.gz)
+tar -zxvf "${TAR}" -C ../
+cd ../
+rm -rf "${GIT_DIR}"
+
+HTTP_SERVER_DIR=$(echo hugegraph-*)
+HTTPS_SERVER_DIR="hugegraph_https"
+
+cp -r "${HTTP_SERVER_DIR}" "${HTTPS_SERVER_DIR}"
+
+# config auth options just for http server (must keep '/.')
+cp -rf "${TRAVIS_DIR}"/conf/. "${HTTP_SERVER_DIR}"/conf/
+
+# start HugeGraphServer with http protocol
+cd "${HTTP_SERVER_DIR}"
+echo -e "pa" | bin/init-store.sh || exit 1
+bin/start-hugegraph.sh || exit 1
+
+# config options for https server
+cd ../"${HTTPS_SERVER_DIR}"
+REST_SERVER_CONFIG="conf/rest-server.properties"
+GREMLIN_SERVER_CONFIG="conf/gremlin-server.yaml"
+sed -i "s?http://127.0.0.1:8080?https://127.0.0.1:8443?g" "$REST_SERVER_CONFIG"
+sed -i "s/#port: 8182/port: 8282/g" "$GREMLIN_SERVER_CONFIG"
+echo "gremlinserver.url=http://127.0.0.1:8282" >> ${REST_SERVER_CONFIG}
+
+# start HugeGraphServer with https protocol
+bin/init-store.sh
+bin/start-hugegraph.sh
+cd ../
diff --git a/hugegraph-client/build.sh b/hugegraph-client/build.sh
new file mode 100644
index 0000000..170bce5
--- /dev/null
+++ b/hugegraph-client/build.sh
@@ -0,0 +1,9 @@
+#!/usr/bin/env bash
+
+HUGEGRAPH_CLIENT_RELEASE_PATH="${PWD}/output/"
+
+export MAVEN_HOME="/home/scmtools/buildkit/maven/apache-maven-3.3.9/"
+export JAVA_HOME="/home/scmtools/buildkit/java/jdk1.8.0_25/"
+export PATH="$JAVA_HOME/bin:$MAVEN_HOME/bin:$PATH"
+
+mvn clean test -Dtest=UnitTestSuite
diff --git a/hugegraph-client/pom.xml b/hugegraph-client/pom.xml
new file mode 100644
index 0000000..25d2a90
--- /dev/null
+++ b/hugegraph-client/pom.xml
@@ -0,0 +1,209 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>com.baidu.hugegraph</groupId>
+    <artifactId>hugegraph-client</artifactId>
+    <version>2.1.0</version>
+    <packaging>jar</packaging>
+
+    <name>hugegraph-client</name>
+    <url>https://github.com/hugegraph/hugegraph-client</url>
+    <description>
+        hugegraph-client is a Java-written client of HugeGraph, providing
+        operations of graph, schema, gremlin, variables and traversals etc.
+    </description>
+
+    <parent>
+        <groupId>org.sonatype.oss</groupId>
+        <artifactId>oss-parent</artifactId>
+        <version>7</version>
+    </parent>
+    <licenses>
+        <license>
+            <name>The Apache Software License, Version 2.0</name>
+            <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
+            <distribution>repo</distribution>
+        </license>
+    </licenses>
+
+    <scm>
+        <url>https://github.com/hugegraph/hugegraph-client</url>
+        <connection>https://github.com/hugegraph/hugegraph-client</connection>
+        <developerConnection>https://github.com/hugegraph/hugegraph-client
+        </developerConnection>
+    </scm>
+    <developers>
+        <developer>
+            <name>lizhangmei</name>
+            <email>javaloveme@gmail.com</email>
+        </developer>
+        <developer>
+            <name>zhoney</name>
+            <email>zhangyi89817@126.com</email>
+        </developer>
+        <developer>
+            <name>liningrui</name>
+            <email>liningrui@vip.qq.com</email>
+        </developer>
+    </developers>
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <compiler.source>1.8</compiler.source>
+        <compiler.target>1.8</compiler.target>
+        <hugegraph.common.version>2.1.0</hugegraph.common.version>
+        <jersey.version>3.0.3</jersey.version>
+        <mockito.version>2.8.47</mockito.version>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.baidu.hugegraph</groupId>
+            <artifactId>hugegraph-common</artifactId>
+            <version>${hugegraph.common.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.glassfish.jersey.containers</groupId>
+            <artifactId>jersey-container-servlet</artifactId>
+            <version>${jersey.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+            <version>${mockito.version}</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.1</version>
+                <configuration>
+                    <source>${compiler.source}</source>
+                    <target>${compiler.target}</target>
+                    <compilerArguments>
+                        <Xmaxerrs>500</Xmaxerrs>
+                    </compilerArguments>
+                    <compilerArgs>
+                        <arg>-Xlint:unchecked</arg>
+                    </compilerArgs>
+                </configuration>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jar-plugin</artifactId>
+                <version>2.4</version>
+                <configuration>
+                    <archive>
+                        <index>true</index>
+                        <manifest>
+                            <addDefaultImplementationEntries>
+                                false
+                            </addDefaultImplementationEntries>
+                            <addDefaultSpecificationEntries>
+                                true
+                            </addDefaultSpecificationEntries>
+                        </manifest>
+                        <manifestEntries>
+                            <!-- Must be on one line, otherwise the automatic
+                                 upgrade script cannot replace the version number -->
+                            <Implementation-Version>2.0.1.0</Implementation-Version>
+                        </manifestEntries>
+                    </archive>
+                </configuration>
+            </plugin>
+
+            <plugin>
+                <groupId>org.jacoco</groupId>
+                <artifactId>jacoco-maven-plugin</artifactId>
+                <version>0.8.2</version>
+                <configuration>
+                    <excludes>
+                        <exclude>com/baidu/hugegraph/example/*.class</exclude>
+                    </excludes>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>pre-unit-test</id>
+                        <goals>
+                            <goal>prepare-agent</goal>
+                        </goals>
+                    </execution>
+                    <execution>
+                        <id>post-unit-test</id>
+                        <phase>test</phase>
+                        <goals>
+                            <goal>report</goal>
+                        </goals>
+                        <configuration>
+                            <outputDirectory>${project.build.directory}</outputDirectory>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+    <profiles>
+        <profile>
+            <id>release</id>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-source-plugin</artifactId>
+                        <version>2.2.1</version>
+                        <executions>
+                            <execution>
+                                <id>attach-sources</id>
+                                <goals>
+                                    <goal>jar-no-fork</goal>
+                                </goals>
+                            </execution>
+                        </executions>
+                    </plugin>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-javadoc-plugin</artifactId>
+                        <version>2.9.1</version>
+                        <executions>
+                            <execution>
+                                <id>attach-javadocs</id>
+                                <goals>
+                                    <goal>jar</goal>
+                                </goals>
+                            </execution>
+                        </executions>
+                    </plugin>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-gpg-plugin</artifactId>
+                        <version>1.5</version>
+                        <executions>
+                            <execution>
+                                <id>sign-artifacts</id>
+                                <phase>verify</phase>
+                                <goals>
+                                    <goal>sign</goal>
+                                </goals>
+                            </execution>
+                        </executions>
+                        <configuration>
+                            <!-- Prevent `gpg` from using pinentry programs -->
+                            <gpgArguments>
+                                <arg>--pinentry-mode</arg>
+                                <arg>loopback</arg>
+                            </gpgArguments>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+    </profiles>
+</project>
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/annotation/UnimplementedFeature.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/annotation/UnimplementedFeature.java
new file mode 100644
index 0000000..a920beb
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/annotation/UnimplementedFeature.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Indicates that a method is a feature to be implemented,
+ * and may cause an exception if it is used directly now.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD})
+public @interface UnimplementedFeature {
+
+    String desc() default "";
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/api/API.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/API.java
new file mode 100644
index 0000000..c580a66
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/API.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api;
+
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.util.E;
+
+public abstract class API {
+
+    public static final String CHARSET = "UTF-8";
+    public static final String BATCH_ENCODING = "gzip";
+    public static final long NO_LIMIT = -1L;
+    public static final String PATH_SPLITOR = "/";
+
+    protected final RestClient client;
+
+    private String path;
+
+    public API(RestClient client) {
+        E.checkNotNull(client, "client");
+        this.client = client;
+        this.path = null;
+    }
+
+    public String path() {
+        E.checkState(this.path != null, "Path can't be null");
+        return this.path;
+    }
+
+    protected void path(String path) {
+        this.path = path;
+    }
+
+    protected void path(String pathTemplate, Object... args) {
+        this.path = String.format(pathTemplate, args);
+    }
+
+    protected abstract String type();
+
+    protected static void checkOffset(long value) {
+        E.checkArgument(value >= 0, "Offset must be >= 0, but got: %s", value);
+    }
+
+    protected static void checkLimit(long value, String name) {
+        E.checkArgument(value > 0 || value == NO_LIMIT,
+                        "%s must be > 0 or == %s, but got: %s",
+                        name, NO_LIMIT, value);
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/api/auth/AccessAPI.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/auth/AccessAPI.java
new file mode 100644
index 0000000..91dfb45
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/auth/AccessAPI.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.auth;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.rest.RestResult;
+import com.baidu.hugegraph.structure.auth.Access;
+import com.baidu.hugegraph.structure.constant.HugeType;
+
+public class AccessAPI extends AuthAPI {
+
+    public AccessAPI(RestClient client, String graph) {
+        super(client, graph);
+    }
+
+    @Override
+    protected String type() {
+        return HugeType.ACCESS.string();
+    }
+
+    public Access create(Access access) {
+        RestResult result = this.client.post(this.path(), access);
+        return result.readObject(Access.class);
+    }
+
+    public Access get(Object id) {
+        RestResult result = this.client.get(this.path(), formatRelationId(id));
+        return result.readObject(Access.class);
+    }
+
+    public List<Access> list(Object group, Object target, int limit) {
+        checkLimit(limit, "Limit");
+        Map<String, Object> params = new LinkedHashMap<>();
+        params.put("limit", limit);
+        params.put("group", formatEntityId(group));
+        params.put("target", formatEntityId(target));
+        RestResult result = this.client.get(this.path(), params);
+        return result.readList(this.type(), Access.class);
+    }
+
+    public Access update(Access access) {
+        String id = formatRelationId(access.id());
+        RestResult result = this.client.put(this.path(), id, access);
+        return result.readObject(Access.class);
+    }
+
+    public void delete(Object id) {
+        this.client.delete(this.path(), formatRelationId(id));
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/api/auth/AuthAPI.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/auth/AuthAPI.java
new file mode 100644
index 0000000..646c6b8
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/auth/AuthAPI.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.auth;
+
+import com.baidu.hugegraph.api.API;
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.structure.auth.AuthElement;
+
+public abstract class AuthAPI extends API {
+
+    private static final String PATH = "graphs/%s/auth/%s";
+
+    public AuthAPI(RestClient client, String graph) {
+        super(client);
+        this.path(PATH, graph, this.type());
+    }
+
+    public static String formatEntityId(Object id) {
+        if (id == null) {
+            return null;
+        } else if (id instanceof AuthElement) {
+            id = ((AuthElement) id).id();
+        }
+        return String.valueOf(id);
+    }
+
+    public static String formatRelationId(Object id) {
+        if (id == null) {
+            return null;
+        } else if (id instanceof AuthElement) {
+            id = ((AuthElement) id).id();
+        }
+        return String.valueOf(id);
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/api/auth/BelongAPI.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/auth/BelongAPI.java
new file mode 100644
index 0000000..41e5587
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/auth/BelongAPI.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.auth;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.rest.RestResult;
+import com.baidu.hugegraph.structure.auth.Belong;
+import com.baidu.hugegraph.structure.constant.HugeType;
+
+public class BelongAPI extends AuthAPI {
+
+    public BelongAPI(RestClient client, String graph) {
+        super(client, graph);
+    }
+
+    @Override
+    protected String type() {
+        return HugeType.BELONG.string();
+    }
+
+    public Belong create(Belong belong) {
+        RestResult result = this.client.post(this.path(), belong);
+        return result.readObject(Belong.class);
+    }
+
+    public Belong get(Object id) {
+        RestResult result = this.client.get(this.path(), formatRelationId(id));
+        return result.readObject(Belong.class);
+    }
+
+    public List<Belong> list(Object user, Object group, int limit) {
+        checkLimit(limit, "Limit");
+        Map<String, Object> params = new LinkedHashMap<>();
+        params.put("limit", limit);
+        params.put("user", formatEntityId(user));
+        params.put("group", formatEntityId(group));
+        RestResult result = this.client.get(this.path(), params);
+        return result.readList(this.type(), Belong.class);
+    }
+
+    public Belong update(Belong belong) {
+        String id = formatRelationId(belong.id());
+        RestResult result = this.client.put(this.path(), id, belong);
+        return result.readObject(Belong.class);
+    }
+
+    public void delete(Object id) {
+        this.client.delete(this.path(), formatRelationId(id));
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/api/auth/GroupAPI.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/auth/GroupAPI.java
new file mode 100644
index 0000000..5eb5ec9
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/auth/GroupAPI.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.auth;
+
+import java.util.List;
+import java.util.Map;
+
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.rest.RestResult;
+import com.baidu.hugegraph.structure.auth.Group;
+import com.baidu.hugegraph.structure.constant.HugeType;
+import com.google.common.collect.ImmutableMap;
+
+public class GroupAPI extends AuthAPI {
+
+    public GroupAPI(RestClient client, String graph) {
+        super(client, graph);
+    }
+
+    @Override
+    protected String type() {
+        return HugeType.GROUP.string();
+    }
+
+    public Group create(Group group) {
+        RestResult result = this.client.post(this.path(), group);
+        return result.readObject(Group.class);
+    }
+
+    public Group get(Object id) {
+        RestResult result = this.client.get(this.path(), formatEntityId(id));
+        return result.readObject(Group.class);
+    }
+
+    public List<Group> list(int limit) {
+        checkLimit(limit, "Limit");
+        Map<String, Object> params = ImmutableMap.of("limit", limit);
+        RestResult result = this.client.get(this.path(), params);
+        return result.readList(this.type(), Group.class);
+    }
+
+    public Group update(Group group) {
+        String id = formatEntityId(group.id());
+        RestResult result = this.client.put(this.path(), id, group);
+        return result.readObject(Group.class);
+    }
+
+    public void delete(Object id) {
+        this.client.delete(this.path(), formatEntityId(id));
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/api/auth/LoginAPI.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/auth/LoginAPI.java
new file mode 100644
index 0000000..355a6ce
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/auth/LoginAPI.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.auth;
+
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.rest.RestResult;
+import com.baidu.hugegraph.structure.auth.Login;
+import com.baidu.hugegraph.structure.auth.LoginResult;
+import com.baidu.hugegraph.structure.constant.HugeType;
+
+public class LoginAPI extends AuthAPI {
+
+    public LoginAPI(RestClient client, String graph) {
+        super(client, graph);
+    }
+
+    @Override
+    protected String type() {
+        return HugeType.LOGIN.string();
+    }
+
+    public LoginResult login(Login login) {
+        RestResult result = this.client.post(this.path(), login);
+        return result.readObject(LoginResult.class);
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/api/auth/LogoutAPI.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/auth/LogoutAPI.java
new file mode 100644
index 0000000..cd53f6b
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/auth/LogoutAPI.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.auth;
+
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.structure.constant.HugeType;
+import com.google.common.collect.ImmutableMap;
+
+public class LogoutAPI extends AuthAPI {
+
+    public LogoutAPI(RestClient client, String graph) {
+        super(client, graph);
+    }
+
+    @Override
+    protected String type() {
+        return HugeType.LOGOUT.string();
+    }
+
+    public void logout() {
+        this.client.delete(this.path(), ImmutableMap.of());
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/api/auth/ProjectAPI.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/auth/ProjectAPI.java
new file mode 100644
index 0000000..c6a3f07
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/auth/ProjectAPI.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.auth;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.rest.RestResult;
+import com.baidu.hugegraph.structure.auth.Project;
+import com.baidu.hugegraph.structure.constant.HugeType;
+import com.google.common.collect.ImmutableMap;
+
+public class ProjectAPI extends AuthAPI {
+
+    private static final String ACTION_ADD_GRAPH = "add_graph";
+    private static final String ACTION_REMOVE_GRAPH = "remove_graph";
+
+    public ProjectAPI(RestClient client, String graph) {
+        super(client, graph);
+    }
+
+    @Override
+    protected String type() {
+        return HugeType.PROJECT.string();
+    }
+
+    public Project create(Project project) {
+        RestResult result = this.client.post(this.path(), project);
+        return result.readObject(Project.class);
+    }
+
+    public Project get(Object id) {
+        RestResult result = this.client.get(this.path(), formatEntityId(id));
+        return result.readObject(Project.class);
+    }
+
+    public List<Project> list(long limit) {
+        checkLimit(limit, "Limit");
+        Map<String, Object> params = ImmutableMap.of("limit", limit);
+        RestResult result = this.client.get(this.path(), params);
+        return result.readList(this.type(), Project.class);
+    }
+
+    public Project update(Project project) {
+        String id = formatEntityId(project.id());
+        RestResult result = this.client.put(this.path(), id, project);
+        return result.readObject(Project.class);
+    }
+
+    public void delete(Object id) {
+        this.client.delete(this.path(), formatEntityId(id));
+    }
+
+    public Project addGraphs(Object projectId, Set<String> graphs) {
+        Project project = new Project();
+        project.graphs(graphs);
+        RestResult result = this.client.put(this.path(),
+                                            formatEntityId(projectId),
+                                            project,
+                                            ImmutableMap.of("action",
+                                                            ACTION_ADD_GRAPH));
+        return result.readObject(Project.class);
+    }
+
+    public Project removeGraphs(Object projectId, Set<String> graphs) {
+        Project project = new Project();
+        project.graphs(graphs);
+        RestResult result = this.client.put(this.path(),
+                                            formatEntityId(projectId),
+                                            project,
+                                            ImmutableMap.of("action",
+                                                            ACTION_REMOVE_GRAPH));
+        return result.readObject(Project.class);
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/api/auth/TargetAPI.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/auth/TargetAPI.java
new file mode 100644
index 0000000..31a80d2
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/auth/TargetAPI.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.auth;
+
+import java.util.List;
+import java.util.Map;
+
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.rest.RestResult;
+import com.baidu.hugegraph.structure.auth.Target;
+import com.baidu.hugegraph.structure.constant.HugeType;
+import com.google.common.collect.ImmutableMap;
+
+public class TargetAPI extends AuthAPI {
+
+    public TargetAPI(RestClient client, String graph) {
+        super(client, graph);
+    }
+
+    @Override
+    protected String type() {
+        return HugeType.TARGET.string();
+    }
+
+    public Target create(Target target) {
+        RestResult result = this.client.post(this.path(), target);
+        return result.readObject(Target.class);
+    }
+
+    public Target get(Object id) {
+        RestResult result = this.client.get(this.path(), formatEntityId(id));
+        return result.readObject(Target.class);
+    }
+
+    public List<Target> list(int limit) {
+        checkLimit(limit, "Limit");
+        Map<String, Object> params = ImmutableMap.of("limit", limit);
+        RestResult result = this.client.get(this.path(), params);
+        return result.readList(this.type(), Target.class);
+    }
+
+    public Target update(Target target) {
+        String id = formatEntityId(target.id());
+        RestResult result = this.client.put(this.path(), id, target);
+        return result.readObject(Target.class);
+    }
+
+    public void delete(Object id) {
+        this.client.delete(this.path(), formatEntityId(id));
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/api/auth/TokenAPI.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/auth/TokenAPI.java
new file mode 100644
index 0000000..8fb0bf5
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/auth/TokenAPI.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.auth;
+
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.rest.RestResult;
+import com.baidu.hugegraph.structure.auth.TokenPayload;
+import com.baidu.hugegraph.structure.constant.HugeType;
+
+public class TokenAPI extends AuthAPI {
+
+    public TokenAPI(RestClient client, String graph) {
+        super(client, graph);
+    }
+
+    @Override
+    protected String type() {
+        return HugeType.TOKEN_VERIFY.string();
+    }
+
+    public TokenPayload verifyToken() {
+        RestResult result = this.client.get(this.path());
+        return result.readObject(TokenPayload.class);
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/api/auth/UserAPI.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/auth/UserAPI.java
new file mode 100644
index 0000000..07855fc
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/auth/UserAPI.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.auth;
+
+import java.util.List;
+import java.util.Map;
+
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.rest.RestResult;
+import com.baidu.hugegraph.structure.auth.User;
+import com.baidu.hugegraph.structure.auth.User.UserRole;
+import com.baidu.hugegraph.structure.constant.HugeType;
+import com.google.common.collect.ImmutableMap;
+
+public class UserAPI extends AuthAPI {
+
+    public UserAPI(RestClient client, String graph) {
+        super(client, graph);
+    }
+
+    @Override
+    protected String type() {
+        return HugeType.USER.string();
+    }
+
+    public User create(User user) {
+        RestResult result = this.client.post(this.path(), user);
+        return result.readObject(User.class);
+    }
+
+    public User get(Object id) {
+        RestResult result = this.client.get(this.path(), formatEntityId(id));
+        return result.readObject(User.class);
+    }
+
+    public UserRole getUserRole(Object id) {
+        String idEncoded = RestClient.encode(formatEntityId(id));
+        String path = String.join("/", this.path(), idEncoded, "role");
+        RestResult result = this.client.get(path);
+        return result.readObject(UserRole.class);
+    }
+
+    public List<User> list(int limit) {
+        checkLimit(limit, "Limit");
+        Map<String, Object> params = ImmutableMap.of("limit", limit);
+        RestResult result = this.client.get(this.path(), params);
+        return result.readList(this.type(), User.class);
+    }
+
+    public User update(User user) {
+        String id = formatEntityId(user.id());
+        RestResult result = this.client.put(this.path(), id, user);
+        return result.readObject(User.class);
+    }
+
+    public void delete(Object id) {
+        this.client.delete(this.path(), formatEntityId(id));
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/api/graph/EdgeAPI.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/graph/EdgeAPI.java
new file mode 100644
index 0000000..6e8e104
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/graph/EdgeAPI.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.graph;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.exception.NotAllCreatedException;
+import com.baidu.hugegraph.rest.RestResult;
+import com.baidu.hugegraph.structure.constant.Direction;
+import com.baidu.hugegraph.structure.constant.HugeType;
+import com.baidu.hugegraph.structure.graph.BatchEdgeRequest;
+import com.baidu.hugegraph.structure.graph.Edge;
+import com.baidu.hugegraph.structure.graph.Edges;
+import com.google.common.collect.ImmutableMap;
+import jakarta.ws.rs.core.MultivaluedHashMap;
+
+public class EdgeAPI extends GraphAPI {
+
+    public EdgeAPI(RestClient client, String graph) {
+        super(client, graph);
+    }
+
+    @Override
+    protected String type() {
+        return HugeType.EDGE.string();
+    }
+
+    public Edge create(Edge edge) {
+        RestResult result = this.client.post(this.path(), edge);
+        return result.readObject(Edge.class);
+    }
+
+    public List<String> create(List<Edge> edges, boolean checkVertex) {
+        MultivaluedHashMap<String, Object> headers = new MultivaluedHashMap<>();
+        headers.putSingle("Content-Encoding", BATCH_ENCODING);
+        Map<String, Object> params = ImmutableMap.of("check_vertex",
+                                                     checkVertex);
+        RestResult result = this.client.post(this.batchPath(), edges,
+                                             headers, params);
+        List<String> ids = result.readList(String.class);
+        if (edges.size() != ids.size()) {
+            throw new NotAllCreatedException(
+                      "Not all edges are successfully created, " +
+                      "expect '%s', the actual is '%s'",
+                      ids, edges.size(), ids.size());
+        }
+        return ids;
+    }
+
+    public List<Edge> update(BatchEdgeRequest request) {
+        this.client.checkApiVersion("0.45", "batch property update");
+        MultivaluedHashMap<String, Object> headers = new MultivaluedHashMap<>();
+        headers.putSingle("Content-Encoding", BATCH_ENCODING);
+        RestResult result = this.client.put(this.batchPath(), null,
+                                            request, headers);
+        return result.readList(this.type(), Edge.class);
+    }
+
+    public Edge append(Edge edge) {
+        String id = edge.id();
+        Map<String, Object> params = ImmutableMap.of("action", "append");
+        RestResult result = this.client.put(this.path(), id, edge, params);
+        return result.readObject(Edge.class);
+    }
+
+    public Edge eliminate(Edge edge) {
+        String id = edge.id();
+        Map<String, Object> params = ImmutableMap.of("action", "eliminate");
+        RestResult result = this.client.put(this.path(), id, edge, params);
+        return result.readObject(Edge.class);
+    }
+
+    public Edge get(String id) {
+        RestResult result = this.client.get(this.path(), id);
+        return result.readObject(Edge.class);
+    }
+
+    public Edges list(int limit) {
+        return this.list(null, null, null, null, 0, null, limit);
+    }
+
+    public Edges list(Object vertexId, Direction direction,
+                      String label, Map<String, Object> properties,
+                      int offset, String page, int limit) {
+        return this.list(vertexId, direction, label, properties, false,
+                         offset, page, limit);
+    }
+
+    public Edges list(Object vertexId, Direction direction, String label,
+                      Map<String, Object> properties, boolean keepP,
+                      int offset, String page, int limit) {
+        checkOffset(offset);
+        checkLimit(limit, "Limit");
+        String vid = GraphAPI.formatVertexId(vertexId, true);
+        String props = GraphAPI.formatProperties(properties);
+        Map<String, Object> params = new LinkedHashMap<>();
+        params.put("vertex_id", vid);
+        params.put("direction", direction);
+        params.put("label", label);
+        params.put("properties", props);
+        params.put("keep_start_p", keepP);
+        params.put("offset", offset);
+        params.put("limit", limit);
+        params.put("page", page);
+        RestResult result = this.client.get(this.path(), params);
+        return result.readObject(Edges.class);
+    }
+
+    public void delete(String id) {
+        this.client.delete(this.path(), id);
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/api/graph/GraphAPI.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/graph/GraphAPI.java
new file mode 100644
index 0000000..9362238
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/graph/GraphAPI.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.graph;
+
+import java.util.Map;
+import java.util.UUID;
+
+import org.glassfish.jersey.uri.UriComponent;
+import org.glassfish.jersey.uri.UriComponent.Type;
+
+import com.baidu.hugegraph.api.API;
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.util.E;
+import com.baidu.hugegraph.util.JsonUtil;
+
+public abstract class GraphAPI extends API {
+
+    private static final String PATH = "graphs/%s/graph/%s";
+
+    private final String batchPath;
+
+    public GraphAPI(RestClient client, String graph) {
+        super(client);
+        this.path(PATH, graph, this.type());
+        this.batchPath = String.join("/", this.path(), "batch");
+    }
+
+    public String batchPath() {
+        return this.batchPath;
+    }
+
+    public static String formatVertexId(Object id) {
+        return formatVertexId(id, false);
+    }
+
+    public static String formatVertexId(Object id, boolean allowNull) {
+        if (!allowNull) {
+            E.checkArgumentNotNull(id, "The vertex id can't be null");
+        } else {
+            if (id == null) {
+                return null;
+            }
+        }
+        boolean uuid = id instanceof UUID;
+        if (uuid) {
+            id = id.toString();
+        }
+        E.checkArgument(id instanceof String || id instanceof Number,
+                        "The vertex id must be either String or " +
+                        "Number, but got '%s'", id);
+        return (uuid ? "U" : "") + JsonUtil.toJson(id);
+    }
+
+    public static String formatProperties(Map<String, Object> properties) {
+        if (properties == null) {
+            return null;
+        }
+        String json = JsonUtil.toJson(properties);
+        /*
+         * Don't use UrlEncoder.encode, it encoded the space as `+`,
+         * which will invalidate the jersey's automatic decoding
+         * because it considers the space to be encoded as `%2F`
+         */
+        return encode(json);
+    }
+
+    public static String encode(String raw) {
+        return UriComponent.encode(raw, Type.QUERY_PARAM_SPACE_ENCODED);
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/api/graph/VertexAPI.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/graph/VertexAPI.java
new file mode 100644
index 0000000..13bb602
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/graph/VertexAPI.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.graph;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.exception.InvalidResponseException;
+import com.baidu.hugegraph.exception.NotAllCreatedException;
+import com.baidu.hugegraph.rest.RestResult;
+import com.baidu.hugegraph.structure.constant.HugeType;
+import com.baidu.hugegraph.structure.graph.BatchOlapPropertyRequest;
+import com.baidu.hugegraph.structure.graph.BatchVertexRequest;
+import com.baidu.hugegraph.structure.graph.Vertex;
+import com.baidu.hugegraph.structure.graph.Vertices;
+import com.google.common.collect.ImmutableMap;
+import jakarta.ws.rs.core.MultivaluedHashMap;
+
+public class VertexAPI extends GraphAPI {
+
+    public VertexAPI(RestClient client, String graph) {
+        super(client, graph);
+    }
+
+    @Override
+    protected String type() {
+        return HugeType.VERTEX.string();
+    }
+
+    public Vertex create(Vertex vertex) {
+        RestResult result = this.client.post(this.path(), vertex);
+        return result.readObject(Vertex.class);
+    }
+
+    public List<Object> create(List<Vertex> vertices) {
+        MultivaluedHashMap<String, Object> headers = new MultivaluedHashMap<>();
+        headers.putSingle("Content-Encoding", BATCH_ENCODING);
+        RestResult result = this.client.post(this.batchPath(), vertices,
+                                             headers);
+        List<Object> ids = result.readList(Object.class);
+        if (vertices.size() != ids.size()) {
+            throw new NotAllCreatedException(
+                      "Not all vertices are successfully created, " +
+                      "expect '%s', the actual is '%s'",
+                      ids, vertices.size(), ids.size());
+        }
+        return ids;
+    }
+
+    public List<Vertex> update(BatchVertexRequest request) {
+        this.client.checkApiVersion("0.45", "batch property update");
+        MultivaluedHashMap<String, Object> headers = new MultivaluedHashMap<>();
+        headers.putSingle("Content-Encoding", BATCH_ENCODING);
+        RestResult result = this.client.put(this.batchPath(), null,
+                                            request, headers);
+        return result.readList(this.type(), Vertex.class);
+    }
+
+    public int update(BatchOlapPropertyRequest request) {
+        this.client.checkApiVersion("0.59", "olap property batch update");
+        MultivaluedHashMap<String, Object> headers = new MultivaluedHashMap<>();
+        headers.putSingle("Content-Encoding", BATCH_ENCODING);
+        String path = String.join("/", this.path(), "olap/batch");
+        RestResult result = this.client.put(path, null, request, headers);
+        Object size = result.readObject(Map.class).get("size");
+        if (!(size instanceof Integer)) {
+            throw new InvalidResponseException(
+                      "The 'size' in response must be int, but got: %s(%s)",
+                      size, size.getClass());
+        }
+        return (int) size;
+    }
+
+    public Vertex append(Vertex vertex) {
+        String id = GraphAPI.formatVertexId(vertex.id());
+        Map<String, Object> params = ImmutableMap.of("action", "append");
+        RestResult result = this.client.put(this.path(), id, vertex, params);
+        return result.readObject(Vertex.class);
+    }
+
+    public Vertex eliminate(Vertex vertex) {
+        String id = GraphAPI.formatVertexId(vertex.id());
+        Map<String, Object> params = ImmutableMap.of("action", "eliminate");
+        RestResult result = this.client.put(this.path(), id, vertex, params);
+        return result.readObject(Vertex.class);
+    }
+
+    public Vertex get(Object id) {
+        String vertexId = GraphAPI.formatVertexId(id);
+        RestResult result = this.client.get(this.path(), vertexId);
+        return result.readObject(Vertex.class);
+    }
+
+    public Vertices list(int limit) {
+        return this.list(null, null, 0, null, limit);
+    }
+
+    public Vertices list(String label, Map<String, Object> properties,
+                         int offset, String page, int limit) {
+        return this.list(label, properties, false, offset, page, limit);
+    }
+
+    public Vertices list(String label, Map<String, Object> properties,
+                         boolean keepP, int offset, String page, int limit) {
+        checkOffset(offset);
+        checkLimit(limit, "Limit");
+        String props = GraphAPI.formatProperties(properties);
+        Map<String, Object> params = new LinkedHashMap<>();
+        params.put("label", label);
+        params.put("properties", props);
+        params.put("keep_start_p", keepP);
+        params.put("offset", offset);
+        params.put("limit", limit);
+        params.put("page", page);
+        RestResult result = this.client.get(this.path(), params);
+        return result.readObject(Vertices.class);
+    }
+
+    public void delete(Object id) {
+        String vertexId = GraphAPI.formatVertexId(id);
+        this.client.delete(this.path(), vertexId);
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/api/graphs/GraphsAPI.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/graphs/GraphsAPI.java
new file mode 100644
index 0000000..8851699
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/graphs/GraphsAPI.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.graphs;
+
+import java.util.List;
+import java.util.Map;
+
+import jakarta.ws.rs.core.HttpHeaders;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.MultivaluedHashMap;
+import org.apache.commons.lang3.StringUtils;
+
+import com.baidu.hugegraph.api.API;
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.exception.InvalidResponseException;
+import com.baidu.hugegraph.rest.RestResult;
+import com.baidu.hugegraph.structure.constant.GraphMode;
+import com.baidu.hugegraph.structure.constant.GraphReadMode;
+import com.baidu.hugegraph.structure.constant.HugeType;
+import com.google.common.collect.ImmutableMap;
+
+public class GraphsAPI extends API {
+
+    private static final String DELIMITER = "/";
+    private static final String MODE = "mode";
+    private static final String GRAPH_READ_MODE = "graph_read_mode";
+    private static final String CLEAR = "clear";
+
+    private static final String CONFIRM_MESSAGE = "confirm_message";
+
+    public GraphsAPI(RestClient client) {
+        super(client);
+        this.path(this.type());
+    }
+
+    @Override
+    protected String type() {
+        return HugeType.GRAPHS.string();
+    }
+
+    @SuppressWarnings("unchecked")
+    public Map<String, String> create(String name, String cloneGraphName,
+                                      String configText) {
+        this.client.checkApiVersion("0.67", "dynamic graph add");
+        MultivaluedHashMap<String, Object> headers = new MultivaluedHashMap<>();
+        headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON);
+        Map<String, Object> params = null;
+        if (StringUtils.isNotEmpty(cloneGraphName)) {
+            params = ImmutableMap.of("clone_graph_name", cloneGraphName);
+        }
+        RestResult result = this.client.post(joinPath(this.path(), name),
+                                             configText, headers, params);
+        return result.readObject(Map.class);
+    }
+
+    @SuppressWarnings("unchecked")
+    public Map<String, String> get(String name) {
+        RestResult result = this.client.get(this.path(), name);
+        return result.readObject(Map.class);
+    }
+
+    public List<String> list() {
+        RestResult result = this.client.get(this.path());
+        return result.readList(this.type(), String.class);
+    }
+
+    public void clear(String graph, String message) {
+        this.client.delete(joinPath(this.path(), graph, CLEAR),
+                           ImmutableMap.of(CONFIRM_MESSAGE, message));
+    }
+
+    public void drop(String graph, String message) {
+        this.client.checkApiVersion("0.67", "dynamic graph delete");
+        this.client.delete(joinPath(this.path(), graph),
+                           ImmutableMap.of(CONFIRM_MESSAGE, message));
+    }
+
+    public void mode(String graph, GraphMode mode) {
+        // NOTE: Must provide id for PUT. If use "graph/mode", "/" will
+        // be encoded to "%2F". So use "mode" here although inaccurate.
+        this.client.put(joinPath(this.path(), graph, MODE), null, mode);
+    }
+
+    public GraphMode mode(String graph) {
+        RestResult result = this.client.get(joinPath(this.path(), graph), MODE);
+        @SuppressWarnings("unchecked")
+        Map<String, String> mode = result.readObject(Map.class);
+        String value = mode.get(MODE);
+        if (value == null) {
+            throw new InvalidResponseException(
+                      "Invalid response, expect 'mode' in response");
+        }
+        try {
+            return GraphMode.valueOf(value);
+        } catch (IllegalArgumentException e) {
+            throw new InvalidResponseException(
+                      "Invalid GraphMode value '%s'", value);
+        }
+    }
+
+    public void readMode(String graph, GraphReadMode readMode) {
+        this.client.checkApiVersion("0.59", "graph read mode");
+        // NOTE: Must provide id for PUT. If use "graph/graph_read_mode", "/"
+        // will be encoded to "%2F". So use "graph_read_mode" here although
+        // inaccurate.
+        this.client.put(joinPath(this.path(), graph, GRAPH_READ_MODE),
+                        null, readMode);
+    }
+
+    public GraphReadMode readMode(String graph) {
+        this.client.checkApiVersion("0.59", "graph read mode");
+        RestResult result = this.client.get(joinPath(this.path(), graph),
+                                            GRAPH_READ_MODE);
+        @SuppressWarnings("unchecked")
+        Map<String, String> readMode = result.readObject(Map.class);
+        String value = readMode.get(GRAPH_READ_MODE);
+        if (value == null) {
+            throw new InvalidResponseException(
+                      "Invalid response, expect 'graph_read_mode' in response");
+        }
+        try {
+            return GraphReadMode.valueOf(value);
+        } catch (IllegalArgumentException e) {
+            throw new InvalidResponseException(
+                      "Invalid GraphReadMode value '%s'", value);
+        }
+    }
+
+    private static String joinPath(String path, String graph) {
+        return String.join(DELIMITER, path, graph);
+    }
+
+    private static String joinPath(String path, String graph, String action) {
+        return String.join(DELIMITER, path, graph, action);
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/api/gremlin/GremlinAPI.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/gremlin/GremlinAPI.java
new file mode 100644
index 0000000..ee89391
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/gremlin/GremlinAPI.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.gremlin;
+
+import com.baidu.hugegraph.api.API;
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.rest.RestResult;
+import com.baidu.hugegraph.structure.constant.HugeType;
+import com.baidu.hugegraph.structure.gremlin.Response;
+
+public class GremlinAPI extends API {
+
+    public GremlinAPI(RestClient client) {
+        super(client);
+        this.path(type());
+    }
+
+    @Override
+    protected String type() {
+        return HugeType.GREMLIN.string();
+    }
+
+    public Response post(GremlinRequest request) {
+        RestResult result = this.client.post(this.path(), request);
+        return result.readObject(Response.class);
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/api/gremlin/GremlinRequest.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/gremlin/GremlinRequest.java
new file mode 100644
index 0000000..8c6311b
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/gremlin/GremlinRequest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.gremlin;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import com.baidu.hugegraph.driver.GremlinManager;
+import com.baidu.hugegraph.structure.gremlin.ResultSet;
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+@JsonInclude(JsonInclude.Include.NON_EMPTY)
+public class GremlinRequest {
+
+    // See org.apache.tinkerpop.gremlin.server.channel.HttpChannelizer
+    public String gremlin;
+    public Map<String, Object> bindings;
+    public String language;
+    public Map<String, String> aliases;
+
+    public GremlinRequest(String gremlin) {
+        this.gremlin = gremlin;
+        this.bindings = new ConcurrentHashMap<>();
+        this.language = "gremlin-groovy";
+        this.aliases = new ConcurrentHashMap<>();
+    }
+
+    public static class Builder {
+        private GremlinRequest request;
+        private GremlinManager manager;
+
+        public Builder(String gremlin, GremlinManager executor) {
+            this.request = new GremlinRequest(gremlin);
+            this.manager = executor;
+        }
+
+        public ResultSet execute() {
+            return this.manager.execute(this.request);
+        }
+
+        public long executeAsTask() {
+            return this.manager.executeAsTask(this.request);
+        }
+
+        public Builder binding(String key, Object value) {
+            this.request.bindings.put(key, value);
+            return this;
+        }
+
+        public Builder language(String language) {
+            this.request.language = language;
+            return this;
+        }
+
+        public Builder alias(String key, String value) {
+            this.request.aliases.put(key, value);
+            return this;
+        }
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/api/job/GremlinJobAPI.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/job/GremlinJobAPI.java
new file mode 100644
index 0000000..80c7383
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/job/GremlinJobAPI.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.job;
+
+import java.util.Map;
+
+import com.baidu.hugegraph.api.gremlin.GremlinRequest;
+import com.baidu.hugegraph.api.task.TaskAPI;
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.rest.RestResult;
+
+public class GremlinJobAPI extends JobAPI {
+
+    private static final String JOB_TYPE = "gremlin";
+
+    public GremlinJobAPI(RestClient client, String graph) {
+        super(client, graph);
+    }
+
+    @Override
+    protected String jobType() {
+        return JOB_TYPE;
+    }
+
+    public long execute(GremlinRequest request) {
+        RestResult result = this.client.post(this.path(), request);
+        @SuppressWarnings("unchecked")
+        Map<String, Object> task = result.readObject(Map.class);
+        return TaskAPI.parseTaskId(task);
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/api/job/JobAPI.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/job/JobAPI.java
new file mode 100644
index 0000000..6447759
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/job/JobAPI.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.job;
+
+import com.baidu.hugegraph.api.API;
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.structure.constant.HugeType;
+
+public abstract class JobAPI extends API {
+
+    // For example: graphs/hugegraph/jobs/gremlin
+    private static final String PATH = "graphs/%s/%s/%s";
+
+    public JobAPI(RestClient client, String graph) {
+        super(client);
+        this.path(String.format(PATH, graph, this.type(), this.jobType()));
+    }
+
+    @Override
+    protected String type() {
+        return HugeType.JOB.string();
+    }
+
+    protected abstract String jobType();
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/api/job/RebuildAPI.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/job/RebuildAPI.java
new file mode 100644
index 0000000..4a077b9
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/job/RebuildAPI.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.job;
+
+import java.util.Map;
+
+import com.baidu.hugegraph.api.task.TaskAPI;
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.rest.RestResult;
+import com.baidu.hugegraph.structure.SchemaElement;
+import com.baidu.hugegraph.structure.schema.EdgeLabel;
+import com.baidu.hugegraph.structure.schema.IndexLabel;
+import com.baidu.hugegraph.structure.schema.VertexLabel;
+import com.baidu.hugegraph.util.E;
+
+public class RebuildAPI extends JobAPI {
+
+    private static final String JOB_TYPE = "rebuild";
+
+    public RebuildAPI(RestClient client, String graph) {
+        super(client, graph);
+    }
+
+    @Override
+    protected String jobType() {
+        return JOB_TYPE;
+    }
+
+    public long rebuild(VertexLabel vertexLabel) {
+        return this.rebuildIndex(vertexLabel);
+    }
+
+    public long rebuild(EdgeLabel edgeLabel) {
+        return this.rebuildIndex(edgeLabel);
+    }
+
+    public long rebuild(IndexLabel indexLabel) {
+        return this.rebuildIndex(indexLabel);
+    }
+
+    private long rebuildIndex(SchemaElement element) {
+        E.checkArgument(element instanceof VertexLabel ||
+                        element instanceof EdgeLabel ||
+                        element instanceof IndexLabel,
+                        "Only VertexLabel, EdgeLabel and IndexLabel support " +
+                        "rebuild, but got '%s'", element);
+        String path = String.join(PATH_SPLITOR, this.path(), element.type());
+        RestResult result = this.client.put(path, element.name(), element);
+        @SuppressWarnings("unchecked")
+        Map<String, Object> task = result.readObject(Map.class);
+        return TaskAPI.parseTaskId(task);
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/api/metrics/MetricsAPI.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/metrics/MetricsAPI.java
new file mode 100644
index 0000000..af8a2d2
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/metrics/MetricsAPI.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.metrics;
+
+import java.util.Map;
+
+import com.baidu.hugegraph.api.API;
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.rest.RestResult;
+import com.baidu.hugegraph.structure.constant.HugeType;
+import com.baidu.hugegraph.util.CommonUtil;
+
+public class MetricsAPI extends API {
+
+    public MetricsAPI(RestClient client) {
+        super(client);
+        this.path(this.type());
+    }
+
+    @Override
+    protected String type() {
+        return HugeType.METRICS.string();
+    }
+
+    @SuppressWarnings("unchecked")
+    public Map<String, Map<String, Object>> system() {
+        RestResult result = this.client.get(this.path(), "system");
+        Map<?, ?> map = result.readObject(Map.class);
+        CommonUtil.checkMapClass(map, String.class, Map.class);
+        for (Object mapValue : map.values()) {
+            CommonUtil.checkMapClass(mapValue, String.class, Object.class);
+        }
+        return (Map<String, Map<String, Object>>) map;
+    }
+
+    @SuppressWarnings("unchecked")
+    public Map<String, Map<String, Object>> backend() {
+        RestResult result = this.client.get(this.path(), "backend");
+        Map<?, ?> map = result.readObject(Map.class);
+        CommonUtil.checkMapClass(map, String.class, Map.class);
+        for (Object mapValue : map.values()) {
+            CommonUtil.checkMapClass(mapValue, String.class, Object.class);
+        }
+        return (Map<String, Map<String, Object>>) map;
+    }
+
+    public Map<String, Object> backend(String graph) {
+        return this.backend().get(graph);
+    }
+
+    @SuppressWarnings("unchecked")
+    public Map<String, Map<String, Object>> all() {
+        RestResult result = this.client.get(this.path());
+        Map<?, ?> map = result.readObject(Map.class);
+        CommonUtil.checkMapClass(map, String.class, Map.class);
+        for (Object mapValue : map.values()) {
+            CommonUtil.checkMapClass(mapValue, String.class, Object.class);
+        }
+        return (Map<String, Map<String, Object>>) map;
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/api/schema/EdgeLabelAPI.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/schema/EdgeLabelAPI.java
new file mode 100644
index 0000000..991887a
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/schema/EdgeLabelAPI.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.schema;
+
+import java.util.List;
+import java.util.Map;
+
+import com.baidu.hugegraph.api.task.TaskAPI;
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.rest.RestResult;
+import com.baidu.hugegraph.structure.SchemaElement;
+import com.baidu.hugegraph.structure.constant.HugeType;
+import com.baidu.hugegraph.structure.schema.EdgeLabel;
+import com.baidu.hugegraph.util.E;
+import com.google.common.collect.ImmutableMap;
+
+public class EdgeLabelAPI extends SchemaElementAPI {
+
+    public EdgeLabelAPI(RestClient client, String graph) {
+        super(client, graph);
+    }
+
+    @Override
+    protected String type() {
+        return HugeType.EDGE_LABEL.string();
+    }
+
+    public EdgeLabel create(EdgeLabel edgeLabel) {
+        Object el = this.checkCreateOrUpdate(edgeLabel);
+        RestResult result = this.client.post(this.path(), el);
+        return result.readObject(EdgeLabel.class);
+    }
+
+    public EdgeLabel append(EdgeLabel edgeLabel) {
+        String id = edgeLabel.name();
+        Map<String, Object> params = ImmutableMap.of("action", "append");
+        Object el = this.checkCreateOrUpdate(edgeLabel);
+        RestResult result = this.client.put(this.path(), id, el, params);
+        return result.readObject(EdgeLabel.class);
+    }
+
+    public EdgeLabel eliminate(EdgeLabel edgeLabel) {
+        String id = edgeLabel.name();
+        Map<String, Object> params = ImmutableMap.of("action", "eliminate");
+        Object el = this.checkCreateOrUpdate(edgeLabel);
+        RestResult result = this.client.put(this.path(), id, el, params);
+        return result.readObject(EdgeLabel.class);
+    }
+
+    public EdgeLabel get(String name) {
+        RestResult result = this.client.get(this.path(), name);
+        return result.readObject(EdgeLabel.class);
+    }
+
+    public List<EdgeLabel> list() {
+        RestResult result = this.client.get(this.path());
+        return result.readList(this.type(), EdgeLabel.class);
+    }
+
+    public List<EdgeLabel> list(List<String> names) {
+        this.client.checkApiVersion("0.48", "getting schema by names");
+        E.checkArgument(names != null && !names.isEmpty(),
+                        "The edge label names can't be null or empty");
+        Map<String, Object> params = ImmutableMap.of("names", names);
+        RestResult result = this.client.get(this.path(), params);
+        return result.readList(this.type(), EdgeLabel.class);
+    }
+
+    public long delete(String name) {
+        RestResult result = this.client.delete(this.path(), name);
+        @SuppressWarnings("unchecked")
+        Map<String, Object> task = result.readObject(Map.class);
+        return TaskAPI.parseTaskId(task);
+    }
+
+    @Override
+    protected Object checkCreateOrUpdate(SchemaElement schemaElement) {
+        EdgeLabel edgeLabel = (EdgeLabel) schemaElement;
+        Object el = edgeLabel;
+        if (this.client.apiVersionLt("0.54")) {
+            E.checkArgument(edgeLabel.ttl() == 0L &&
+                            edgeLabel.ttlStartTime() == null,
+                            "Not support ttl until api version 0.54");
+            el = edgeLabel.switchV53();
+        }
+        return el;
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/api/schema/IndexLabelAPI.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/schema/IndexLabelAPI.java
new file mode 100644
index 0000000..3e85543
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/schema/IndexLabelAPI.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.schema;
+
+import java.util.List;
+import java.util.Map;
+
+import com.baidu.hugegraph.api.task.TaskAPI;
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.exception.NotSupportException;
+import com.baidu.hugegraph.rest.RestResult;
+import com.baidu.hugegraph.structure.SchemaElement;
+import com.baidu.hugegraph.structure.constant.HugeType;
+import com.baidu.hugegraph.structure.constant.IndexType;
+import com.baidu.hugegraph.structure.schema.IndexLabel;
+import com.baidu.hugegraph.util.E;
+import com.google.common.collect.ImmutableMap;
+
+public class IndexLabelAPI extends SchemaElementAPI {
+
+    public IndexLabelAPI(RestClient client, String graph) {
+        super(client, graph);
+    }
+
+    @Override
+    protected String type() {
+        return HugeType.INDEX_LABEL.string();
+    }
+
+    public IndexLabel.IndexLabelWithTask create(IndexLabel indexLabel) {
+        Object il = this.checkCreateOrUpdate(indexLabel);
+        RestResult result = this.client.post(this.path(), il);
+        return result.readObject(IndexLabel.IndexLabelWithTask.class);
+    }
+
+    public IndexLabel append(IndexLabel indexLabel) {
+        if (this.client.apiVersionLt("0.50")) {
+            throw new NotSupportException("action append on index label");
+        }
+
+        String id = indexLabel.name();
+        Map<String, Object> params = ImmutableMap.of("action", "append");
+        Object il = this.checkCreateOrUpdate(indexLabel);
+        RestResult result = this.client.put(this.path(), id, il, params);
+        return result.readObject(IndexLabel.class);
+    }
+
+    public IndexLabel eliminate(IndexLabel indexLabel) {
+        if (this.client.apiVersionLt("0.50")) {
+            throw new NotSupportException("action eliminate on index label");
+        }
+
+        String id = indexLabel.name();
+        Map<String, Object> params = ImmutableMap.of("action", "eliminate");
+        Object il = this.checkCreateOrUpdate(indexLabel);
+        RestResult result = this.client.put(this.path(), id, il, params);
+        return result.readObject(IndexLabel.class);
+    }
+
+    public IndexLabel get(String name) {
+        RestResult result = this.client.get(this.path(), name);
+        return result.readObject(IndexLabel.class);
+    }
+
+    public List<IndexLabel> list() {
+        RestResult result = this.client.get(this.path());
+        return result.readList(this.type(), IndexLabel.class);
+    }
+
+    public List<IndexLabel> list(List<String> names) {
+        this.client.checkApiVersion("0.48", "getting schema by names");
+        E.checkArgument(names != null && !names.isEmpty(),
+                        "The index label names can't be null or empty");
+        Map<String, Object> params = ImmutableMap.of("names", names);
+        RestResult result = this.client.get(this.path(), params);
+        return result.readList(this.type(), IndexLabel.class);
+    }
+
+    public long delete(String name) {
+        RestResult result = this.client.delete(this.path(), name);
+        @SuppressWarnings("unchecked")
+        Map<String, Object> task = result.readObject(Map.class);
+        return TaskAPI.parseTaskId(task);
+    }
+
+    @Override
+    protected Object checkCreateOrUpdate(SchemaElement schemaElement) {
+        IndexLabel indexLabel = (IndexLabel) schemaElement;
+        if (indexLabel.indexType() == IndexType.SHARD) {
+            this.client.checkApiVersion("0.43", "shard index");
+        } else if (indexLabel.indexType() == IndexType.UNIQUE) {
+            this.client.checkApiVersion("0.44", "unique index");
+        }
+
+        IndexLabel il = indexLabel;
+        if (this.client.apiVersionLt("0.50")) {
+            E.checkArgument(indexLabel.userdata() == null ||
+                            indexLabel.userdata().isEmpty(),
+                            "Not support userdata of index label until api " +
+                            "version 0.50");
+            E.checkArgument(indexLabel.rebuild(),
+                            "Not support rebuild of index label until api " +
+                            "version 0.57");
+            il = indexLabel.switchV49();
+        } else if (this.client.apiVersionLt("0.57")) {
+            E.checkArgument(indexLabel.rebuild(),
+                            "Not support rebuild of index label until api " +
+                            "version 0.57");
+            il = indexLabel.switchV56();
+        }
+        return il;
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/api/schema/PropertyKeyAPI.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/schema/PropertyKeyAPI.java
new file mode 100644
index 0000000..fcb9c51
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/schema/PropertyKeyAPI.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.schema;
+
+import java.util.List;
+import java.util.Map;
+
+import com.baidu.hugegraph.api.task.TaskAPI;
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.exception.NotSupportException;
+import com.baidu.hugegraph.rest.RestResult;
+import com.baidu.hugegraph.structure.SchemaElement;
+import com.baidu.hugegraph.structure.constant.HugeType;
+import com.baidu.hugegraph.structure.constant.WriteType;
+import com.baidu.hugegraph.structure.schema.PropertyKey;
+import com.baidu.hugegraph.util.E;
+import com.google.common.collect.ImmutableMap;
+
+public class PropertyKeyAPI extends SchemaElementAPI {
+
+    public PropertyKeyAPI(RestClient client, String graph) {
+        super(client, graph);
+    }
+
+    @Override
+    protected String type() {
+        return HugeType.PROPERTY_KEY.string();
+    }
+
+    public PropertyKey.PropertyKeyWithTask create(PropertyKey propertyKey) {
+        Object pkey = this.checkCreateOrUpdate(propertyKey);
+        RestResult result = this.client.post(this.path(), pkey);
+        if (this.client.apiVersionLt("0.65")) {
+            return new PropertyKey.PropertyKeyWithTask(
+                       result.readObject(PropertyKey.class), 0L);
+        }
+        return result.readObject(PropertyKey.PropertyKeyWithTask.class);
+    }
+
+    public PropertyKey.PropertyKeyWithTask append(PropertyKey propertyKey) {
+        String id = propertyKey.name();
+        Map<String, Object> params = ImmutableMap.of("action", "append");
+        Object pkey = this.checkCreateOrUpdate(propertyKey);
+        RestResult result = this.client.put(this.path(), id, pkey, params);
+        return result.readObject(PropertyKey.PropertyKeyWithTask.class);
+    }
+
+    public PropertyKey.PropertyKeyWithTask eliminate(PropertyKey propertyKey) {
+        String id = propertyKey.name();
+        Map<String, Object> params = ImmutableMap.of("action", "eliminate");
+        Object pkey = this.checkCreateOrUpdate(propertyKey);
+        RestResult result = this.client.put(this.path(), id, pkey, params);
+        return result.readObject(PropertyKey.PropertyKeyWithTask.class);
+    }
+
+    public PropertyKey.PropertyKeyWithTask clear(PropertyKey propertyKey) {
+        if (this.client.apiVersionLt("0.65")) {
+            throw new NotSupportException("action clear on property key");
+        }
+        String id = propertyKey.name();
+        Map<String, Object> params = ImmutableMap.of("action", "clear");
+        Object pkey = this.checkCreateOrUpdate(propertyKey);
+        RestResult result = this.client.put(this.path(), id, pkey, params);
+        return result.readObject(PropertyKey.PropertyKeyWithTask.class);
+    }
+
+    public PropertyKey get(String name) {
+        RestResult result = this.client.get(this.path(), name);
+        return result.readObject(PropertyKey.class);
+    }
+
+    public List<PropertyKey> list() {
+        RestResult result = this.client.get(this.path());
+        return result.readList(this.type(), PropertyKey.class);
+    }
+
+    public List<PropertyKey> list(List<String> names) {
+        this.client.checkApiVersion("0.48", "getting schema by names");
+        E.checkArgument(names != null && !names.isEmpty(),
+                        "The property key names can't be null or empty");
+        Map<String, Object> params = ImmutableMap.of("names", names);
+        RestResult result = this.client.get(this.path(), params);
+        return result.readList(this.type(), PropertyKey.class);
+    }
+
+    public long delete(String name) {
+        if (this.client.apiVersionLt("0.65")) {
+            this.client.delete(this.path(), name);
+            return 0L;
+        }
+        RestResult result = this.client.delete(this.path(), name);
+        @SuppressWarnings("unchecked")
+        Map<String, Object> task = result.readObject(Map.class);
+        return TaskAPI.parseTaskId(task);
+    }
+
+    @Override
+    protected Object checkCreateOrUpdate(SchemaElement schemaElement) {
+        PropertyKey propertyKey = (PropertyKey) schemaElement;
+        Object pkey = propertyKey;
+        if (this.client.apiVersionLt("0.47")) {
+            E.checkArgument(propertyKey.aggregateType().isNone(),
+                            "Not support aggregate property until " +
+                            "api version 0.47");
+            pkey = propertyKey.switchV46();
+        } else if (this.client.apiVersionLt("0.59")) {
+            E.checkArgument(propertyKey.writeType() == WriteType.OLTP,
+                            "Not support olap property key until " +
+                            "api version 0.59");
+            pkey = propertyKey.switchV58();
+        }
+        return pkey;
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/api/schema/SchemaAPI.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/schema/SchemaAPI.java
new file mode 100644
index 0000000..537b147
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/schema/SchemaAPI.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.schema;
+
+import java.util.List;
+import java.util.Map;
+
+import com.baidu.hugegraph.api.API;
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.exception.NotSupportException;
+import com.baidu.hugegraph.rest.RestResult;
+import com.baidu.hugegraph.structure.SchemaElement;
+
+public class SchemaAPI extends API {
+
+    private static final String PATH = "graphs/%s/%s";
+
+    public SchemaAPI(RestClient client, String graph) {
+        super(client);
+        this.path(PATH, graph, this.type());
+    }
+
+    @SuppressWarnings("unchecked")
+    public Map<String, List<SchemaElement>> list() {
+        if (this.client.apiVersionLt("0.66")) {
+            throw new NotSupportException("schema get api");
+        }
+        RestResult result = this.client.get(this.path());
+        return result.readObject(Map.class);
+    }
+
+    @Override
+    protected String type() {
+        return "schema";
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/api/schema/SchemaElementAPI.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/schema/SchemaElementAPI.java
new file mode 100644
index 0000000..2bc6135
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/schema/SchemaElementAPI.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.schema;
+
+import com.baidu.hugegraph.api.API;
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.structure.SchemaElement;
+
+public abstract class SchemaElementAPI extends API {
+
+    private static final String PATH = "graphs/%s/schema/%s";
+
+    public SchemaElementAPI(RestClient client, String graph) {
+        super(client);
+        this.path(PATH, graph, this.type());
+    }
+
+    protected abstract Object checkCreateOrUpdate(SchemaElement schemaElement);
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/api/schema/VertexLabelAPI.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/schema/VertexLabelAPI.java
new file mode 100644
index 0000000..a47f08c
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/schema/VertexLabelAPI.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.schema;
+
+import java.util.List;
+import java.util.Map;
+
+import com.baidu.hugegraph.api.task.TaskAPI;
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.rest.RestResult;
+import com.baidu.hugegraph.structure.SchemaElement;
+import com.baidu.hugegraph.structure.constant.HugeType;
+import com.baidu.hugegraph.structure.schema.VertexLabel;
+import com.baidu.hugegraph.util.E;
+import com.google.common.collect.ImmutableMap;
+
+public class VertexLabelAPI extends SchemaElementAPI {
+
+    public VertexLabelAPI(RestClient client, String graph) {
+        super(client, graph);
+    }
+
+    @Override
+    protected String type() {
+        return HugeType.VERTEX_LABEL.string();
+    }
+
+    public VertexLabel create(VertexLabel vertexLabel) {
+        Object vl = this.checkCreateOrUpdate(vertexLabel);
+        RestResult result = this.client.post(this.path(), vl);
+        return result.readObject(VertexLabel.class);
+    }
+
+    public VertexLabel append(VertexLabel vertexLabel) {
+        String id = vertexLabel.name();
+        Map<String, Object> params = ImmutableMap.of("action", "append");
+        Object vl = this.checkCreateOrUpdate(vertexLabel);
+        RestResult result = this.client.put(this.path(), id, vl, params);
+        return result.readObject(VertexLabel.class);
+    }
+
+    public VertexLabel eliminate(VertexLabel vertexLabel) {
+        String id = vertexLabel.name();
+        Map<String, Object> params = ImmutableMap.of("action", "eliminate");
+        Object vl = this.checkCreateOrUpdate(vertexLabel);
+        RestResult result = this.client.put(this.path(), id, vl, params);
+        return result.readObject(VertexLabel.class);
+    }
+
+    public VertexLabel get(String name) {
+        RestResult result = this.client.get(this.path(), name);
+        return result.readObject(VertexLabel.class);
+    }
+
+    public List<VertexLabel> list() {
+        RestResult result = this.client.get(this.path());
+        return result.readList(this.type(), VertexLabel.class);
+    }
+
+    public List<VertexLabel> list(List<String> names) {
+        this.client.checkApiVersion("0.48", "getting schema by names");
+        E.checkArgument(names != null && !names.isEmpty(),
+                        "The vertex label names can't be null or empty");
+        Map<String, Object> params = ImmutableMap.of("names", names);
+        RestResult result = this.client.get(this.path(), params);
+        return result.readList(this.type(), VertexLabel.class);
+    }
+
+    public long delete(String name) {
+        RestResult result = this.client.delete(this.path(), name);
+        @SuppressWarnings("unchecked")
+        Map<String, Object> task = result.readObject(Map.class);
+        return TaskAPI.parseTaskId(task);
+    }
+
+    @Override
+    protected Object checkCreateOrUpdate(SchemaElement schemaElement) {
+        VertexLabel vertexLabel = (VertexLabel) schemaElement;
+        if (vertexLabel.idStrategy().isCustomizeUuid()) {
+            this.client.checkApiVersion("0.46", "customize UUID strategy");
+        }
+        Object vl = vertexLabel;
+        if (this.client.apiVersionLt("0.54")) {
+            E.checkArgument(vertexLabel.ttl() == 0L &&
+                            vertexLabel.ttlStartTime() == null,
+                            "Not support ttl until api version 0.54");
+            vl = vertexLabel.switchV53();
+        }
+        return vl;
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/api/task/TaskAPI.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/task/TaskAPI.java
new file mode 100644
index 0000000..51ffe05
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/task/TaskAPI.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.task;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.baidu.hugegraph.api.API;
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.rest.ClientException;
+import com.baidu.hugegraph.rest.RestResult;
+import com.baidu.hugegraph.structure.Task;
+import com.baidu.hugegraph.structure.constant.HugeType;
+import com.baidu.hugegraph.util.E;
+import com.baidu.hugegraph.util.TaskCache;
+import com.google.common.collect.ImmutableMap;
+
+public class TaskAPI extends API {
+
+    private static final String PATH = "graphs/%s/tasks";
+    private String graph;
+    public static final String TASKS = "tasks";
+    public static final String TASK_ID = "task_id";
+    public static final long TASK_TIMEOUT = 60L;
+    private static final long QUERY_INTERVAL = 500L;
+
+    public TaskAPI(RestClient client, String graph) {
+        super(client);
+        this.path(String.format(PATH, graph));
+        this.graph = graph;
+    }
+
+    @Override
+    protected String type() {
+        return HugeType.TASK.string();
+    }
+
+    public String graph() {
+        return this.graph;
+    }
+
+    public List<Task> list(String status, long limit) {
+        Map<String, Object> params = new LinkedHashMap<>();
+        params.put("limit", limit);
+        if (status != null) {
+            params.put("status", status);
+        }
+        RestResult result = this.client.get(this.path(), params);
+        return result.readList(TASKS, Task.class);
+    }
+
+    public TasksWithPage list(String status, String page, long limit) {
+        E.checkArgument(page != null, "The page can not be null");
+        this.client.checkApiVersion("0.48", "getting tasks by paging");
+        Map<String, Object> params = new LinkedHashMap<>();
+        params.put("limit", limit);
+        params.put("page", page);
+        if (status != null) {
+            params.put("status", status);
+        }
+        RestResult result = this.client.get(this.path(), params);
+        return result.readObject(TasksWithPage.class);
+    }
+
+    public List<Task> list(List<Long> ids) {
+        Map<String, Object> params = new LinkedHashMap<>();
+        params.put("ids", ids);
+        RestResult result = this.client.get(this.path(), params);
+        return result.readList(TASKS, Task.class);
+    }
+
+    public Task get(long id) {
+        RestResult result = this.client.get(this.path(), String.valueOf(id));
+        return result.readObject(Task.class);
+    }
+
+    public void delete(long id) {
+        this.client.delete(path(), String.valueOf(id));
+    }
+
+    public Task cancel(long id) {
+        Map<String, Object> params = ImmutableMap.of("action", "cancel");
+        RestResult result = this.client.put(path(), String.valueOf(id),
+                                            ImmutableMap.of(), params);
+        return result.readObject(Task.class);
+    }
+
+    public Task waitUntilTaskSuccess(long taskId, long seconds) {
+        if (taskId == 0) {
+            return null;
+        }
+        long passes = seconds * 1000 / QUERY_INTERVAL;
+        try {
+            for (long pass = 0; ; pass++) {
+                Task task = this.getFromCache(taskId);
+                if (task.success()) {
+                    return task;
+                } else if (task.completed()) {
+                    throw new ClientException(
+                              "Task '%s' is %s, result is '%s'",
+                              taskId, task.status(), task.result());
+                }
+                if (pass >= passes) {
+                    break;
+                }
+                try {
+                    // Query every half second from cache to decrease waiting
+                    // time because restful query is executed per second
+                    Thread.sleep(QUERY_INTERVAL);
+                } catch (InterruptedException e) {
+                    // Ignore
+                }
+            }
+            throw new ClientException(
+                      "Task '%s' not completed in %s seconds, " +
+                      "it can still be queried by task-get API",
+                      taskId, seconds);
+        } finally {
+            // Stop querying this task info whatever
+            this.removeFromCache(taskId);
+        }
+    }
+
+    private Task getFromCache(long taskId) {
+        return TaskCache.instance().get(this, taskId);
+    }
+
+    private void removeFromCache(long taskId) {
+        TaskCache.instance().remove(this, taskId);
+    }
+
+    public static long parseTaskId(Map<String, Object> task) {
+        E.checkState(task.size() == 1 && task.containsKey(TASK_ID),
+                     "Task must be formatted to {\"%s\" : id}, but got %s",
+                     TASK_ID, task);
+        Object taskId = task.get(TASK_ID);
+        E.checkState(taskId instanceof Number,
+                     "Task id must be number, but got '%s'", taskId);
+        return ((Number) taskId).longValue();
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/api/task/TasksWithPage.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/task/TasksWithPage.java
new file mode 100644
index 0000000..93f09b4
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/task/TasksWithPage.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.task;
+
+import java.util.List;
+
+import com.baidu.hugegraph.structure.Task;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class TasksWithPage {
+
+    @JsonProperty
+    private String page;
+    @JsonProperty
+    private List<Task> tasks;
+
+    public String page() {
+        return this.page;
+    }
+
+    public List<Task> tasks() {
+        return this.tasks;
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/AllShortestPathsAPI.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/AllShortestPathsAPI.java
new file mode 100644
index 0000000..26a2e7f
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/AllShortestPathsAPI.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.traverser;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.baidu.hugegraph.api.graph.GraphAPI;
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.rest.RestResult;
+import com.baidu.hugegraph.structure.constant.Direction;
+import com.baidu.hugegraph.structure.graph.Path;
+
+public class AllShortestPathsAPI extends TraversersAPI {
+
+    public AllShortestPathsAPI(RestClient client, String graph) {
+        super(client, graph);
+    }
+
+    @Override
+    protected String type() {
+        return "allshortestpaths";
+    }
+
+    public List<Path> get(Object sourceId, Object targetId,
+                          Direction direction, String label, int maxDepth,
+                          long degree, long skipDegree, long capacity) {
+        this.client.checkApiVersion("0.51", "all shortest path");
+        String source = GraphAPI.formatVertexId(sourceId, false);
+        String target = GraphAPI.formatVertexId(targetId, false);
+
+        checkPositive(maxDepth, "Max depth of shortest path");
+        checkDegree(degree);
+        checkCapacity(capacity);
+        checkSkipDegree(skipDegree, degree, capacity);
+
+        Map<String, Object> params = new LinkedHashMap<>();
+        params.put("source", source);
+        params.put("target", target);
+        params.put("direction", direction);
+        params.put("label", label);
+        params.put("max_depth", maxDepth);
+        params.put("max_degree", degree);
+        params.put("skip_degree", skipDegree);
+        params.put("capacity", capacity);
+        RestResult result = this.client.get(this.path(), params);
+        return result.readList("paths", Path.class);
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/CountAPI.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/CountAPI.java
new file mode 100644
index 0000000..f6ec8cc
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/CountAPI.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.traverser;
+
+import java.util.Map;
+
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.rest.RestResult;
+import com.baidu.hugegraph.structure.traverser.CountRequest;
+import com.baidu.hugegraph.util.E;
+
+public class CountAPI extends TraversersAPI {
+
+    private static final String COUNT = "count";
+
+    public CountAPI(RestClient client, String graph) {
+        super(client, graph);
+    }
+
+    @Override
+    protected String type() {
+        return "count";
+    }
+
+    public long post(CountRequest request) {
+        this.client.checkApiVersion("0.55", "count");
+        RestResult result = this.client.post(this.path(), request);
+        @SuppressWarnings("unchecked")
+        Map<String, Number> countMap = result.readObject(Map.class);
+        E.checkState(countMap.containsKey(COUNT),
+                     "The result doesn't have key '%s'", COUNT);
+        return countMap.get(COUNT).longValue();
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/CrosspointsAPI.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/CrosspointsAPI.java
new file mode 100644
index 0000000..9eb6d24
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/CrosspointsAPI.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.traverser;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.baidu.hugegraph.api.graph.GraphAPI;
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.rest.RestResult;
+import com.baidu.hugegraph.structure.constant.Direction;
+import com.baidu.hugegraph.structure.graph.Path;
+
+public class CrosspointsAPI extends TraversersAPI {
+
+    public CrosspointsAPI(RestClient client, String graph) {
+        super(client, graph);
+    }
+
+    @Override
+    protected String type() {
+        return "crosspoints";
+    }
+
+    public List<Path> get(Object sourceId, Object targetId,
+                          Direction direction, String label,
+                          int maxDepth, long degree,
+                          long capacity, long limit) {
+        String source = GraphAPI.formatVertexId(sourceId, false);
+        String target = GraphAPI.formatVertexId(targetId, false);
+
+        checkPositive(maxDepth, "Max depth of path");
+        checkDegree(degree);
+        checkCapacity(capacity);
+        checkLimit(limit);
+
+        Map<String, Object> params = new LinkedHashMap<>();
+        params.put("source", source);
+        params.put("target", target);
+        params.put("direction", direction);
+        params.put("label", label);
+        params.put("max_depth", maxDepth);
+        params.put("max_degree", degree);
+        params.put("capacity", capacity);
+        params.put("limit", limit);
+        RestResult result = this.client.get(this.path(), params);
+        return result.readList("crosspoints", Path.class);
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/CustomizedCrosspointsAPI.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/CustomizedCrosspointsAPI.java
new file mode 100644
index 0000000..8eca0f8
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/CustomizedCrosspointsAPI.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.traverser;
+
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.rest.RestResult;
+import com.baidu.hugegraph.structure.traverser.CrosspointsRequest;
+import com.baidu.hugegraph.structure.traverser.CustomizedCrosspoints;
+
+public class CustomizedCrosspointsAPI extends TraversersAPI {
+
+    public CustomizedCrosspointsAPI(RestClient client, String graph) {
+        super(client, graph);
+    }
+
+    @Override
+    protected String type() {
+        return "customizedcrosspoints";
+    }
+
+    public CustomizedCrosspoints post(CrosspointsRequest request) {
+        RestResult result = this.client.post(this.path(), request);
+        return result.readObject(CustomizedCrosspoints.class);
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/CustomizedPathsAPI.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/CustomizedPathsAPI.java
new file mode 100644
index 0000000..ef1b032
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/CustomizedPathsAPI.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.traverser;
+
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.rest.RestResult;
+import com.baidu.hugegraph.structure.traverser.PathsWithVertices;
+import com.baidu.hugegraph.structure.traverser.CustomizedPathsRequest;
+
+public class CustomizedPathsAPI extends TraversersAPI {
+
+    public CustomizedPathsAPI(RestClient client, String graph) {
+        super(client, graph);
+    }
+
+    @Override
+    protected String type() {
+        return "customizedpaths";
+    }
+
+    public PathsWithVertices post(CustomizedPathsRequest request) {
+        RestResult result = this.client.post(this.path(), request);
+        return result.readObject(PathsWithVertices.class);
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/EdgesAPI.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/EdgesAPI.java
new file mode 100644
index 0000000..680c335
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/EdgesAPI.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.traverser;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.rest.RestResult;
+import com.baidu.hugegraph.structure.graph.Edge;
+import com.baidu.hugegraph.structure.graph.Edges;
+import com.baidu.hugegraph.structure.graph.Shard;
+import com.baidu.hugegraph.util.E;
+import com.google.common.collect.ImmutableMap;
+
+public class EdgesAPI extends TraversersAPI {
+
+    public EdgesAPI(RestClient client, String graph) {
+        super(client, graph);
+    }
+
+    @Override
+    protected String type() {
+        return "edges";
+    }
+
+    public List<Edge> list(List<String> ids) {
+        E.checkArgument(ids != null && !ids.isEmpty(),
+                        "Ids can't be null or empty");
+
+        Map<String, Object> params = new LinkedHashMap<>();
+        params.put("ids", ids);
+        RestResult result = this.client.get(this.path(), params);
+        return result.readList(this.type(), Edge.class);
+    }
+
+    public List<Shard> shards(long splitSize) {
+        String path = String.join(PATH_SPLITOR, this.path(), "shards");
+        Map<String, Object> params = ImmutableMap.of("split_size", splitSize);
+        RestResult result = this.client.get(path, params);
+        return result.readList("shards", Shard.class);
+    }
+
+    public Edges scan(Shard shard, String page, long pageLimit) {
+        E.checkArgument(shard != null, "Shard can't be null");
+        String path = String.join(PATH_SPLITOR, this.path(), "scan");
+        Map<String, Object> params = new LinkedHashMap<>();
+        params.put("start", shard.start());
+        params.put("end", shard.end());
+        params.put("page", page);
+        params.put("page_limit", pageLimit);
+        RestResult result = this.client.get(path, params);
+        return result.readObject(Edges.class);
+    }
+}
+
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/FusiformSimilarityAPI.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/FusiformSimilarityAPI.java
new file mode 100644
index 0000000..81053cd
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/FusiformSimilarityAPI.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.traverser;
+
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.rest.RestResult;
+import com.baidu.hugegraph.structure.traverser.FusiformSimilarity;
+import com.baidu.hugegraph.structure.traverser.FusiformSimilarityRequest;
+
+public class FusiformSimilarityAPI extends TraversersAPI {
+
+    public FusiformSimilarityAPI(RestClient client, String graph) {
+        super(client, graph);
+    }
+
+    @Override
+    protected String type() {
+        return "fusiformsimilarity";
+    }
+
+    public FusiformSimilarity post(FusiformSimilarityRequest request) {
+        this.client.checkApiVersion("0.49", "fusiform similarity");
+        RestResult result = this.client.post(this.path(), request);
+        return result.readObject(FusiformSimilarity.class);
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/JaccardSimilarityAPI.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/JaccardSimilarityAPI.java
new file mode 100644
index 0000000..d39f236
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/JaccardSimilarityAPI.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.traverser;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import com.baidu.hugegraph.api.graph.GraphAPI;
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.rest.RestResult;
+import com.baidu.hugegraph.structure.constant.Direction;
+import com.baidu.hugegraph.structure.traverser.SingleSourceJaccardSimilarityRequest;
+
+
+import com.baidu.hugegraph.util.E;
+
+public class JaccardSimilarityAPI extends TraversersAPI {
+
+    private static final String JACCARD_SIMILARITY = "jaccard_similarity";
+
+    public JaccardSimilarityAPI(RestClient client, String graph) {
+        super(client, graph);
+    }
+
+    @Override
+    protected String type() {
+        return "jaccardsimilarity";
+    }
+
+    public double get(Object vertexId, Object otherId, Direction direction,
+                      String label, long degree) {
+        this.client.checkApiVersion("0.51", "jaccard similarity");
+        String vertex = GraphAPI.formatVertexId(vertexId, false);
+        String other = GraphAPI.formatVertexId(otherId, false);
+        checkDegree(degree);
+        Map<String, Object> params = new LinkedHashMap<>();
+        params.put("vertex", vertex);
+        params.put("other", other);
+        params.put("direction", direction);
+        params.put("label", label);
+        params.put("max_degree", degree);
+        RestResult result = this.client.get(this.path(), params);
+        @SuppressWarnings("unchecked")
+        Map<String, Double> jaccard = result.readObject(Map.class);
+        E.checkState(jaccard.containsKey(JACCARD_SIMILARITY),
+                     "The result doesn't have key '%s'", JACCARD_SIMILARITY);
+        return jaccard.get(JACCARD_SIMILARITY);
+    }
+
+    @SuppressWarnings("unchecked")
+    public Map<Object, Double> post(
+                               SingleSourceJaccardSimilarityRequest request) {
+        this.client.checkApiVersion("0.58", "jaccard similar");
+        RestResult result = this.client.post(this.path(), request);
+        return result.readObject(Map.class);
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/KneighborAPI.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/KneighborAPI.java
new file mode 100644
index 0000000..493e1bf
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/KneighborAPI.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.traverser;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.baidu.hugegraph.api.graph.GraphAPI;
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.rest.RestResult;
+import com.baidu.hugegraph.structure.constant.Direction;
+import com.baidu.hugegraph.structure.traverser.Kneighbor;
+import com.baidu.hugegraph.structure.traverser.KneighborRequest;
+
+public class KneighborAPI extends TraversersAPI {
+
+    public KneighborAPI(RestClient client, String graph) {
+        super(client, graph);
+    }
+
+    @Override
+    protected String type() {
+        return "kneighbor";
+    }
+
+    public List<Object> get(Object sourceId, Direction direction,
+                            String label, int depth, long degree, long limit) {
+        String source = GraphAPI.formatVertexId(sourceId, false);
+
+        checkPositive(depth, "Depth of k-neighbor");
+        checkDegree(degree);
+        checkLimit(limit);
+
+        Map<String, Object> params = new LinkedHashMap<>();
+        params.put("source", source);
+        params.put("direction", direction);
+        params.put("label", label);
+        params.put("max_depth", depth);
+        params.put("max_degree", degree);
+        params.put("limit", limit);
+        RestResult result = this.client.get(this.path(), params);
+        return result.readList("vertices", Object.class);
+    }
+
+    public Kneighbor post(KneighborRequest request) {
+        this.client.checkApiVersion("0.58", "customized kneighbor");
+        RestResult result = this.client.post(this.path(), request);
+        return result.readObject(Kneighbor.class);
+    }
+}
+
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/KoutAPI.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/KoutAPI.java
new file mode 100644
index 0000000..9e9ca01
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/KoutAPI.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.traverser;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.baidu.hugegraph.api.graph.GraphAPI;
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.rest.RestResult;
+import com.baidu.hugegraph.structure.constant.Direction;
+import com.baidu.hugegraph.structure.traverser.Kout;
+import com.baidu.hugegraph.structure.traverser.KoutRequest;
+
+public class KoutAPI extends TraversersAPI {
+
+    public KoutAPI(RestClient client, String graph) {
+        super(client, graph);
+    }
+
+    @Override
+    protected String type() {
+        return "kout";
+    }
+
+    public List<Object> get(Object sourceId, Direction direction,
+                            String label, int depth, boolean nearest,
+                            long degree, long capacity, long limit) {
+        String source = GraphAPI.formatVertexId(sourceId, false);
+
+        checkPositive(depth, "Depth of k-out");
+        checkDegree(degree);
+        checkCapacity(capacity);
+        checkLimit(limit);
+
+        Map<String, Object> params = new LinkedHashMap<>();
+        params.put("source", source);
+        params.put("direction", direction);
+        params.put("label", label);
+        params.put("max_depth", depth);
+        params.put("nearest", nearest);
+        params.put("max_degree", degree);
+        params.put("capacity", capacity);
+        params.put("limit", limit);
+        RestResult result = this.client.get(this.path(), params);
+        return result.readList("vertices", Object.class);
+    }
+
+    public Kout post(KoutRequest request) {
+        this.client.checkApiVersion("0.58", "customized kout");
+        RestResult result = this.client.post(this.path(), request);
+        return result.readObject(Kout.class);
+    }
+}
+
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/MultiNodeShortestPathAPI.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/MultiNodeShortestPathAPI.java
new file mode 100644
index 0000000..d81ac75
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/MultiNodeShortestPathAPI.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.traverser;
+
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.rest.RestResult;
+import com.baidu.hugegraph.structure.traverser.MultiNodeShortestPathRequest;
+import com.baidu.hugegraph.structure.traverser.PathsWithVertices;
+
+public class MultiNodeShortestPathAPI extends TraversersAPI {
+
+    public MultiNodeShortestPathAPI(RestClient client, String graph) {
+        super(client, graph);
+    }
+
+    @Override
+    protected String type() {
+        return "multinodeshortestpath";
+    }
+
+    public PathsWithVertices post(MultiNodeShortestPathRequest request) {
+        this.client.checkApiVersion("0.58", "multi node shortest path");
+        RestResult result = this.client.post(this.path(), request);
+        return result.readObject(PathsWithVertices.class);
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/NeighborRankAPI.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/NeighborRankAPI.java
new file mode 100644
index 0000000..41a4a04
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/NeighborRankAPI.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.traverser;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.rest.RestResult;
+import com.baidu.hugegraph.structure.constant.Direction;
+import com.baidu.hugegraph.structure.constant.Traverser;
+import com.baidu.hugegraph.structure.traverser.Ranks;
+import com.baidu.hugegraph.util.E;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class NeighborRankAPI extends TraversersAPI {
+
+    public NeighborRankAPI(RestClient client, String graph) {
+        super(client, graph);
+    }
+
+    @Override
+    protected String type() {
+        return "neighborrank";
+    }
+
+    public List<Ranks> post(Request request) {
+        RestResult result = this.client.post(this.path(), request);
+        return result.readList("ranks", Ranks.class);
+    }
+
+    public static class Request {
+
+        @JsonProperty("source")
+        private Object source;
+        @JsonProperty("steps")
+        private List<Step> steps;
+        @JsonProperty("alpha")
+        private double alpha;
+        @JsonProperty("capacity")
+        private long capacity;
+
+        private Request() {
+            this.source = null;
+            this.steps = new ArrayList<>();
+            this.alpha = Traverser.DEFAULT_ALPHA;
+            this.capacity = Traverser.DEFAULT_CAPACITY;
+        }
+
+        public static Builder builder() {
+            return new Builder();
+        }
+
+        @Override
+        public String toString() {
+            return String.format("Request{source=%s,steps=%s,alpha=%s," +
+                                 "capacity=%s}", this.source, this.steps,
+                                 this.alpha, this.capacity);
+        }
+
+        public static class Builder {
+
+            private Request request;
+            private List<Step.Builder> stepBuilders;
+
+            private Builder() {
+                this.request = new Request();
+                this.stepBuilders = new ArrayList<>();
+            }
+
+            public Builder source(Object source) {
+                E.checkArgument(source != null, "The label of request " +
+                                "for neighbor rank can't be null");
+                this.request.source = source;
+                return this;
+            }
+
+            public Step.Builder steps() {
+                Step.Builder builder = new Step.Builder();
+                this.stepBuilders.add(builder);
+                return builder;
+            }
+
+            public Builder alpha(double alpha) {
+                TraversersAPI.checkAlpha(alpha);
+                this.request.alpha = alpha;
+                return this;
+            }
+
+            public Builder capacity(long capacity) {
+                TraversersAPI.checkCapacity(capacity);
+                this.request.capacity = capacity;
+                return this;
+            }
+
+            public Request build() {
+                for (Step.Builder builder : this.stepBuilders) {
+                    this.request.steps.add(builder.build());
+                }
+                E.checkArgument(this.request.source != null,
+                                "Source vertex can't be null");
+                E.checkArgument(this.request.steps != null &&
+                                !this.request.steps.isEmpty(),
+                                "Steps can't be null or empty");
+                TraversersAPI.checkCapacity(this.request.capacity);
+                TraversersAPI.checkAlpha(this.request.alpha);
+                return this.request;
+            }
+        }
+
+        public static class Step {
+
+            @JsonProperty("direction")
+            private String direction;
+            @JsonProperty("labels")
+            private List<String> labels;
+            @JsonProperty("degree")
+            private long degree;
+            @JsonProperty("top")
+            private int top;
+
+            private Step() {
+                this.direction = null;
+                this.labels = new ArrayList<>();
+                this.degree = Traverser.DEFAULT_MAX_DEGREE;
+                this.top = (int) Traverser.DEFAULT_PATHS_LIMIT;
+            }
+
+            @Override
+            public String toString() {
+                return String.format("Step{direction=%s,labels=%s,degree=%s," +
+                                     "top=%s}", this.direction, this.labels,
+                                     this.degree, this.top);
+            }
+
+            public static class Builder {
+
+                private Step step;
+
+                private Builder() {
+                    this.step = new Step();
+                }
+
+                public Step.Builder direction(Direction direction) {
+                    this.step.direction = direction.toString();
+                    return this;
+                }
+
+                public Step.Builder labels(List<String> labels) {
+                    this.step.labels.addAll(labels);
+                    return this;
+                }
+
+                public Step.Builder labels(String... labels) {
+                    this.step.labels.addAll(Arrays.asList(labels));
+                    return this;
+                }
+
+                public Step.Builder degree(long degree) {
+                    TraversersAPI.checkDegree(degree);
+                    this.step.degree = degree;
+                    return this;
+                }
+
+                public Step.Builder top(int top) {
+                    E.checkArgument(top > 0 && top <= Traverser.DEFAULT_MAX_TOP,
+                                    "The top of each layer can't exceed %s",
+                                    Traverser.DEFAULT_MAX_TOP);
+                    this.step.top = top;
+                    return this;
+                }
+
+                private Step build() {
+                    TraversersAPI.checkDegree(this.step.degree);
+                    E.checkArgument(this.step.top > 0 &&
+                                    this.step.top <= Traverser.DEFAULT_MAX_TOP,
+                                    "The top of each layer can't exceed %s",
+                                    Traverser.DEFAULT_MAX_TOP);
+                    return this.step;
+                }
+            }
+        }
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/PathsAPI.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/PathsAPI.java
new file mode 100644
index 0000000..5d35133
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/PathsAPI.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.traverser;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.baidu.hugegraph.api.graph.GraphAPI;
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.rest.RestResult;
+import com.baidu.hugegraph.structure.constant.Direction;
+import com.baidu.hugegraph.structure.graph.Path;
+import com.baidu.hugegraph.structure.traverser.PathsRequest;
+import com.baidu.hugegraph.structure.traverser.PathsWithVertices;
+
+public class PathsAPI extends TraversersAPI {
+
+    public PathsAPI(RestClient client, String graph) {
+        super(client, graph);
+    }
+
+    @Override
+    protected String type() {
+        return "paths";
+    }
+
+    public List<Path> get(Object sourceId, Object targetId,
+                          Direction direction, String label,
+                          int maxDepth, long degree, long capacity,
+                          long limit) {
+        String source = GraphAPI.formatVertexId(sourceId, false);
+        String target = GraphAPI.formatVertexId(targetId, false);
+
+        checkPositive(maxDepth, "Max depth of path");
+        checkDegree(degree);
+        checkCapacity(capacity);
+        checkLimit(limit);
+
+        Map<String, Object> params = new LinkedHashMap<>();
+        params.put("source", source);
+        params.put("target", target);
+        params.put("direction", direction);
+        params.put("label", label);
+        params.put("max_depth", maxDepth);
+        params.put("max_degree", degree);
+        params.put("capacity", capacity);
+        params.put("limit", limit);
+        RestResult result = this.client.get(this.path(), params);
+        return result.readList("paths", Path.class);
+    }
+
+    public PathsWithVertices post(PathsRequest request) {
+        this.client.checkApiVersion("0.58", "paths with property filter");
+        RestResult result = this.client.post(this.path(), request);
+        return result.readObject(PathsWithVertices.class);
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/PersonalRankAPI.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/PersonalRankAPI.java
new file mode 100644
index 0000000..43112b6
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/PersonalRankAPI.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.traverser;
+
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.rest.RestResult;
+import com.baidu.hugegraph.structure.constant.Traverser;
+import com.baidu.hugegraph.structure.traverser.Ranks;
+import com.baidu.hugegraph.util.E;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class PersonalRankAPI extends TraversersAPI {
+
+    public PersonalRankAPI(RestClient client, String graph) {
+        super(client, graph);
+    }
+
+    @Override
+    protected String type() {
+        return "personalrank";
+    }
+
+    public Ranks post(Request request) {
+        RestResult result = this.client.post(this.path(), request);
+        return result.readObject(Ranks.class);
+    }
+
+    public static class Request {
+
+        @JsonProperty("source")
+        private Object source;
+        @JsonProperty("label")
+        private String label;
+        @JsonProperty("alpha")
+        private double alpha = Traverser.DEFAULT_ALPHA;
+        @JsonProperty("max_degree")
+        public long degree = Traverser.DEFAULT_MAX_DEGREE;
+        @JsonProperty("limit")
+        private long limit = Traverser.DEFAULT_LIMIT;
+        @JsonProperty("max_depth")
+        private int maxDepth = 5;
+        @JsonProperty("with_label")
+        private WithLabel withLabel = WithLabel.BOTH_LABEL;
+        @JsonProperty("sorted")
+        private boolean sorted = true;
+
+        public static Builder builder() {
+            return new Builder();
+        }
+
+        @Override
+        public String toString() {
+            return String.format("Request{source=%s,label=%s,alpha=%s," +
+                                 "degree=%s,limit=%s,maxDepth=%s," +
+                                 "withLabel=%s,sorted=%s}",
+                                 this.source, this.label, this.alpha,
+                                 this.degree, this.limit, this.maxDepth,
+                                 this.withLabel, this.sorted);
+        }
+
+        public enum WithLabel {
+            SAME_LABEL,
+            OTHER_LABEL,
+            BOTH_LABEL
+        }
+
+        public static class Builder {
+
+            private Request request;
+
+            private Builder() {
+                this.request = new Request();
+            }
+
+            public Builder source(Object source) {
+                E.checkArgument(source != null, "The source of request " +
+                                "for personal rank can't be null");
+                this.request.source = source;
+                return this;
+            }
+
+            public Builder label(String label) {
+                E.checkArgument(label != null, "The label of request " +
+                                "for personal rank can't be null");
+                this.request.label = label;
+                return this;
+            }
+
+            public Builder alpha(double alpha) {
+                TraversersAPI.checkAlpha(alpha);
+                this.request.alpha = alpha;
+                return this;
+            }
+
+            public Builder degree(long degree) {
+                TraversersAPI.checkDegree(degree);
+                this.request.degree = degree;
+                return this;
+            }
+
+            public Builder limit(long limit) {
+                TraversersAPI.checkLimit(limit);
+                this.request.limit = limit;
+                return this;
+            }
+
+            public Builder maxDepth(int maxDepth) {
+                E.checkArgument(maxDepth > 0 &&
+                                maxDepth <= Traverser.DEFAULT_MAX_DEPTH,
+                                "The max depth must be in range (0, %s], " +
+                                "but got: %s",
+                                Traverser.DEFAULT_MAX_DEPTH, maxDepth);
+                this.request.maxDepth = maxDepth;
+                return this;
+            }
+
+            public Builder withLabel(WithLabel withLabel) {
+                this.request.withLabel = withLabel;
+                return this;
+            }
+
+            public Builder sorted(boolean sorted) {
+                this.request.sorted = sorted;
+                return this;
+            }
+
+            public Request build() {
+                E.checkArgument(this.request.source != null,
+                                "Source vertex can't be null");
+                E.checkArgument(this.request.label != null,
+                                "The label of rank request " +
+                                "for personal rank can't be null");
+                TraversersAPI.checkAlpha(this.request.alpha);
+                TraversersAPI.checkDegree(this.request.degree);
+                TraversersAPI.checkLimit(this.request.limit);
+                E.checkArgument(this.request.maxDepth > 0 &&
+                                this.request.maxDepth <=
+                                Traverser.DEFAULT_MAX_DEPTH,
+                                "The max depth must be in range (0, %s], " +
+                                "but got: %s",
+                                Traverser.DEFAULT_MAX_DEPTH,
+                                this.request.maxDepth);
+                return this.request;
+            }
+        }
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/RaysAPI.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/RaysAPI.java
new file mode 100644
index 0000000..a4fd9a4
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/RaysAPI.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.traverser;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.baidu.hugegraph.api.graph.GraphAPI;
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.rest.RestResult;
+import com.baidu.hugegraph.structure.constant.Direction;
+import com.baidu.hugegraph.structure.graph.Path;
+
+public class RaysAPI extends TraversersAPI {
+
+    public RaysAPI(RestClient client, String graph) {
+        super(client, graph);
+    }
+
+    @Override
+    protected String type() {
+        return "rays";
+    }
+
+    public List<Path> get(Object sourceId, Direction direction, String label,
+                          int depth, long degree, long capacity, long limit) {
+        String source = GraphAPI.formatVertexId(sourceId, false);
+
+        checkPositive(depth, "Max depth of path");
+        checkDegree(degree);
+        checkCapacity(capacity);
+        checkLimit(limit);
+
+        Map<String, Object> params = new LinkedHashMap<>();
+        params.put("source", source);
+        params.put("direction", direction);
+        params.put("label", label);
+        params.put("max_depth", depth);
+        params.put("max_degree", degree);
+        params.put("capacity", capacity);
+        params.put("limit", limit);
+        RestResult result = this.client.get(this.path(), params);
+        return result.readList(this.type(), Path.class);
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/RingsAPI.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/RingsAPI.java
new file mode 100644
index 0000000..03bb1e9
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/RingsAPI.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.traverser;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.baidu.hugegraph.api.graph.GraphAPI;
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.rest.RestResult;
+import com.baidu.hugegraph.structure.constant.Direction;
+import com.baidu.hugegraph.structure.graph.Path;
+
+public class RingsAPI extends TraversersAPI {
+
+    public RingsAPI(RestClient client, String graph) {
+        super(client, graph);
+    }
+
+    @Override
+    protected String type() {
+        return "rings";
+    }
+
+    public List<Path> get(Object sourceId, Direction direction, String label,
+                          int depth, boolean sourceInRing, long degree,
+                          long capacity, long limit) {
+        String source = GraphAPI.formatVertexId(sourceId, false);
+
+        checkPositive(depth, "Max depth of path");
+        checkDegree(degree);
+        checkCapacity(capacity);
+        checkLimit(limit);
+
+        if (sourceInRing) {
+            this.client.checkApiVersion("0.40",
+                                        "source_in_ring arg of ring API");
+        }
+
+        Map<String, Object> params = new LinkedHashMap<>();
+        params.put("source", source);
+        params.put("direction", direction);
+        params.put("label", label);
+        params.put("max_depth", depth);
+        params.put("source_in_ring", sourceInRing);
+        params.put("max_degree", degree);
+        params.put("capacity", capacity);
+        params.put("limit", limit);
+        RestResult result = this.client.get(this.path(), params);
+        return result.readList(this.type(), Path.class);
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/SameNeighborsAPI.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/SameNeighborsAPI.java
new file mode 100644
index 0000000..928f16c
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/SameNeighborsAPI.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.traverser;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.baidu.hugegraph.api.graph.GraphAPI;
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.rest.RestResult;
+import com.baidu.hugegraph.structure.constant.Direction;
+
+public class SameNeighborsAPI extends TraversersAPI {
+
+    private static final String SAME_NEIGHBORS = "same_neighbors";
+
+    public SameNeighborsAPI(RestClient client, String graph) {
+        super(client, graph);
+    }
+
+    @Override
+    protected String type() {
+        return "sameneighbors";
+    }
+
+    public List<Object> get(Object vertexId, Object otherId,
+                            Direction direction, String label,
+                            long degree, long limit) {
+        this.client.checkApiVersion("0.51", "same neighbors");
+        String vertex = GraphAPI.formatVertexId(vertexId, false);
+        String other = GraphAPI.formatVertexId(otherId, false);
+        checkDegree(degree);
+        Map<String, Object> params = new LinkedHashMap<>();
+        params.put("vertex", vertex);
+        params.put("other", other);
+        params.put("direction", direction);
+        params.put("label", label);
+        params.put("max_degree", degree);
+        params.put("limit", limit);
+        RestResult result = this.client.get(this.path(), params);
+        return result.readList(SAME_NEIGHBORS, Object.class);
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/ShortestPathAPI.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/ShortestPathAPI.java
new file mode 100644
index 0000000..389b108
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/ShortestPathAPI.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.traverser;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.baidu.hugegraph.api.graph.GraphAPI;
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.rest.RestResult;
+import com.baidu.hugegraph.structure.constant.Direction;
+import com.baidu.hugegraph.structure.graph.Path;
+
+public class ShortestPathAPI extends TraversersAPI {
+
+    public ShortestPathAPI(RestClient client, String graph) {
+        super(client, graph);
+    }
+
+    @Override
+    protected String type() {
+        return "shortestpath";
+    }
+
+    public Path get(Object sourceId, Object targetId,
+                    Direction direction, String label, int maxDepth,
+                    long degree, long skipDegree, long capacity) {
+        String source = GraphAPI.formatVertexId(sourceId, false);
+        String target = GraphAPI.formatVertexId(targetId, false);
+
+        checkPositive(maxDepth, "Max depth of shortest path");
+        checkDegree(degree);
+        checkCapacity(capacity);
+        checkSkipDegree(skipDegree, degree, capacity);
+
+        Map<String, Object> params = new LinkedHashMap<>();
+        params.put("source", source);
+        params.put("target", target);
+        params.put("direction", direction);
+        params.put("label", label);
+        params.put("max_depth", maxDepth);
+        params.put("max_degree", degree);
+        params.put("skip_degree", skipDegree);
+        params.put("capacity", capacity);
+        RestResult result = this.client.get(this.path(), params);
+        List<Object> vertices = result.readList("path", Object.class);
+        return new Path(vertices);
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/SingleSourceShortestPathAPI.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/SingleSourceShortestPathAPI.java
new file mode 100644
index 0000000..463ae34
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/SingleSourceShortestPathAPI.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.traverser;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import com.baidu.hugegraph.api.graph.GraphAPI;
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.rest.RestResult;
+import com.baidu.hugegraph.structure.constant.Direction;
+import com.baidu.hugegraph.structure.traverser.WeightedPaths;
+import com.baidu.hugegraph.util.E;
+
+public class SingleSourceShortestPathAPI extends TraversersAPI {
+
+    public SingleSourceShortestPathAPI(RestClient client, String graph) {
+        super(client, graph);
+    }
+
+    @Override
+    protected String type() {
+        return "singlesourceshortestpath";
+    }
+
+    public WeightedPaths get(Object sourceId, Direction direction, String label,
+                             String weight, long degree, long skipDegree,
+                             long capacity, long limit, boolean withVertex) {
+        this.client.checkApiVersion("0.51", "single source shortest path");
+        String source = GraphAPI.formatVertexId(sourceId, false);
+
+        E.checkNotNull(weight, "weight");
+        checkDegree(degree);
+        checkCapacity(capacity);
+        checkSkipDegree(skipDegree, degree, capacity);
+        checkLimit(limit);
+
+        Map<String, Object> params = new LinkedHashMap<>();
+        params.put("source", source);
+        params.put("direction", direction);
+        params.put("label", label);
+        params.put("weight", weight);
+        params.put("max_degree", degree);
+        params.put("skip_degree", skipDegree);
+        params.put("capacity", capacity);
+        params.put("limit", limit);
+        params.put("with_vertex", withVertex);
+        RestResult result = this.client.get(this.path(), params);
+        return result.readObject(WeightedPaths.class);
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/TemplatePathsAPI.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/TemplatePathsAPI.java
new file mode 100644
index 0000000..a52233a
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/TemplatePathsAPI.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.traverser;
+
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.rest.RestResult;
+import com.baidu.hugegraph.structure.traverser.PathsWithVertices;
+import com.baidu.hugegraph.structure.traverser.TemplatePathsRequest;
+
+public class TemplatePathsAPI extends TraversersAPI {
+
+    public TemplatePathsAPI(RestClient client, String graph) {
+        super(client, graph);
+    }
+
+    @Override
+    protected String type() {
+        return "templatepaths";
+    }
+
+    public PathsWithVertices post(TemplatePathsRequest request) {
+        this.client.checkApiVersion("0.58", "template paths");
+        RestResult result = this.client.post(this.path(), request);
+        return result.readObject(PathsWithVertices.class);
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/TraversersAPI.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/TraversersAPI.java
new file mode 100644
index 0000000..93d5735
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/TraversersAPI.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.traverser;
+
+import com.baidu.hugegraph.api.API;
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.util.E;
+
+public class TraversersAPI extends API {
+
+    private static final String PATH = "graphs/%s/traversers/%s";
+
+    public TraversersAPI(RestClient client, String graph) {
+        super(client);
+        this.path(PATH, graph, this.type());
+    }
+
+    @Override
+    protected String type() {
+        return "traversers";
+    }
+
+    public static void checkPositive(int value, String name) {
+        E.checkArgument(value > 0,
+                        "%s must be > 0, but got '%s'", name, value);
+    }
+
+    public static void checkDegree(long degree) {
+        checkLimit(degree, "Degree");
+    }
+
+    public static void checkCapacity(long capacity) {
+        checkLimit(capacity, "Capacity");
+    }
+
+    public static void checkLimit(long limit) {
+        checkLimit(limit, "Limit");
+    }
+
+    public static void checkAlpha(double alpha) {
+        E.checkArgument(alpha > 0 && alpha <= 1.0,
+                        "The alpha of rank request must be in range (0, 1], " +
+                        "but got '%s'", alpha);
+    }
+
+    public static void checkSkipDegree(long skipDegree, long degree,
+                                       long capacity) {
+        E.checkArgument(skipDegree >= 0L,
+                        "The skipped degree must be >= 0, but got '%s'",
+                        skipDegree);
+        if (capacity != NO_LIMIT) {
+            E.checkArgument(degree != NO_LIMIT && degree < capacity,
+                            "The max degree must be < capacity");
+            E.checkArgument(skipDegree < capacity,
+                            "The skipped degree must be < capacity");
+        }
+        if (skipDegree > 0L) {
+            E.checkArgument(degree != NO_LIMIT && skipDegree >= degree,
+                            "The skipped degree must be >= max degree, " +
+                            "but got skipped degree '%s' and max degree '%s'",
+                            skipDegree, degree);
+        }
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/VerticesAPI.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/VerticesAPI.java
new file mode 100644
index 0000000..5a85865
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/VerticesAPI.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.traverser;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.baidu.hugegraph.api.graph.GraphAPI;
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.rest.RestResult;
+import com.baidu.hugegraph.structure.graph.Shard;
+import com.baidu.hugegraph.structure.graph.Vertex;
+import com.baidu.hugegraph.structure.graph.Vertices;
+import com.baidu.hugegraph.util.E;
+import com.google.common.collect.ImmutableMap;
+
+public class VerticesAPI extends TraversersAPI {
+
+    public VerticesAPI(RestClient client, String graph) {
+        super(client, graph);
+    }
+
+    @Override
+    protected String type() {
+        return "vertices";
+    }
+
+    public List<Vertex> list(List<Object> ids) {
+        E.checkArgument(ids != null && !ids.isEmpty(),
+                        "Ids can't be null or empty");
+
+        List<String> stringIds = new ArrayList<>(ids.size());
+        for (Object id : ids) {
+            stringIds.add(GraphAPI.formatVertexId(id, false));
+        }
+
+        Map<String, Object> params = new LinkedHashMap<>();
+        params.put("ids", stringIds);
+        RestResult result = this.client.get(this.path(), params);
+        return result.readList(this.type(), Vertex.class);
+    }
+
+    public List<Shard> shards(long splitSize) {
+        String path = String.join(PATH_SPLITOR, this.path(), "shards");
+        Map<String, Object> params = ImmutableMap.of("split_size", splitSize);
+        RestResult result = this.client.get(path, params);
+        return result.readList("shards", Shard.class);
+    }
+
+    public Vertices scan(Shard shard, String page, long pageLimit) {
+        E.checkArgument(shard != null, "Shard can't be null");
+        String path = String.join(PATH_SPLITOR, this.path(), "scan");
+        Map<String, Object> params = new LinkedHashMap<>();
+        params.put("start", shard.start());
+        params.put("end", shard.end());
+        params.put("page", page);
+        params.put("page_limit", pageLimit);
+        RestResult result = this.client.get(path, params);
+        return result.readObject(Vertices.class);
+    }
+}
+
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/WeightedShortestPathAPI.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/WeightedShortestPathAPI.java
new file mode 100644
index 0000000..70de048
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/traverser/WeightedShortestPathAPI.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.traverser;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import com.baidu.hugegraph.api.graph.GraphAPI;
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.rest.RestResult;
+import com.baidu.hugegraph.structure.constant.Direction;
+import com.baidu.hugegraph.structure.traverser.WeightedPath;
+import com.baidu.hugegraph.util.E;
+
+public class WeightedShortestPathAPI extends TraversersAPI {
+
+    public WeightedShortestPathAPI(RestClient client, String graph) {
+        super(client, graph);
+    }
+
+    @Override
+    protected String type() {
+        return "weightedshortestpath";
+    }
+
+    public WeightedPath get(Object sourceId, Object targetId,
+                            Direction direction, String label,
+                            String weight, long degree, long skipDegree,
+                            long capacity, boolean withVertex) {
+        this.client.checkApiVersion("0.51", "weighted shortest path");
+        String source = GraphAPI.formatVertexId(sourceId, false);
+        String target = GraphAPI.formatVertexId(targetId, false);
+
+        E.checkNotNull(weight, "weight");
+        checkDegree(degree);
+        checkCapacity(capacity);
+        checkSkipDegree(skipDegree, degree, capacity);
+
+        Map<String, Object> params = new LinkedHashMap<>();
+        params.put("source", source);
+        params.put("target", target);
+        params.put("direction", direction);
+        params.put("label", label);
+        params.put("weight", weight);
+        params.put("max_degree", degree);
+        params.put("skip_degree", skipDegree);
+        params.put("capacity", capacity);
+        params.put("with_vertex", withVertex);
+        RestResult result = this.client.get(this.path(), params);
+        return result.readObject(WeightedPath.class);
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/api/variables/VariablesAPI.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/variables/VariablesAPI.java
new file mode 100644
index 0000000..521a93a
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/variables/VariablesAPI.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.variables;
+
+import java.util.Map;
+
+import com.baidu.hugegraph.api.API;
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.rest.RestResult;
+import com.baidu.hugegraph.structure.constant.HugeType;
+import com.google.common.collect.ImmutableMap;
+
+public class VariablesAPI extends API {
+
+    private static final String PATH = "graphs/%s/%s";
+
+    public VariablesAPI(RestClient client, String graph) {
+        super(client);
+        this.path(PATH, graph, this.type());
+    }
+
+    @Override
+    protected String type() {
+        return HugeType.VARIABLES.string();
+    }
+
+    @SuppressWarnings("unchecked")
+    public Map<String, Object> get(String key) {
+        RestResult result = this.client.get(path(), key);
+        return result.readObject(Map.class);
+    }
+
+    @SuppressWarnings("unchecked")
+    public Map<String, Object> set(String key, Object value) {
+        value = ImmutableMap.of("data", value);
+        RestResult result = this.client.put(this.path(), key, value);
+        return result.readObject(Map.class);
+    }
+
+    public void remove(String key) {
+        this.client.delete(path(), key);
+    }
+
+    @SuppressWarnings("unchecked")
+    public Map<String, Object> all() {
+        RestResult result = this.client.get(path());
+        return result.readObject(Map.class);
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/api/version/VersionAPI.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/version/VersionAPI.java
new file mode 100644
index 0000000..31fa5c3
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/api/version/VersionAPI.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.version;
+
+import com.baidu.hugegraph.api.API;
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.rest.RestResult;
+import com.baidu.hugegraph.structure.constant.HugeType;
+import com.baidu.hugegraph.structure.version.Versions;
+
+public class VersionAPI extends API {
+
+    public VersionAPI(RestClient client) {
+        super(client);
+        this.path(this.type());
+    }
+
+    @Override
+    protected String type() {
+        return HugeType.VERSION.string();
+    }
+
+    public Versions get() {
+        RestResult result = this.client.get(this.path());
+        return result.readObject(Versions.class);
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/client/RestClient.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/client/RestClient.java
new file mode 100644
index 0000000..f29c267
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/client/RestClient.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.client;
+
+import com.baidu.hugegraph.exception.ServerException;
+import com.baidu.hugegraph.rest.AbstractRestClient;
+import com.baidu.hugegraph.rest.ClientException;
+import com.baidu.hugegraph.rest.RestResult;
+import com.baidu.hugegraph.serializer.PathDeserializer;
+import com.baidu.hugegraph.structure.graph.Path;
+import com.baidu.hugegraph.util.E;
+import com.baidu.hugegraph.util.VersionUtil;
+import com.baidu.hugegraph.util.VersionUtil.Version;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import jakarta.ws.rs.core.Response;
+
+public class RestClient extends AbstractRestClient {
+
+    private static final int SECOND = 1000;
+
+    private Version apiVersion = null;
+
+    static {
+        SimpleModule module = new SimpleModule();
+        module.addDeserializer(Path.class, new PathDeserializer());
+        RestResult.registerModule(module);
+    }
+
+    public RestClient(String url, String username, String password,
+                      int timeout) {
+        super(url, username, password, timeout * SECOND);
+    }
+
+    public RestClient(String url, String username, String password, int timeout,
+                      int maxConns, int maxConnsPerRoute,
+                      String trustStoreFile, String trustStorePassword) {
+        super(url, username, password, timeout * SECOND, maxConns,
+              maxConnsPerRoute, trustStoreFile, trustStorePassword);
+    }
+
+    public void apiVersion(Version version) {
+        E.checkNotNull(version, "api version");
+        this.apiVersion = version;
+    }
+
+    public Version apiVersion() {
+        return this.apiVersion;
+    }
+
+    public void checkApiVersion(String minVersion, String message) {
+        if (this.apiVersionLt(minVersion)) {
+            throw new ClientException(
+                      "HugeGraphServer API version must be >= %s to support " +
+                      "%s, but current HugeGraphServer API version is: %s",
+                      minVersion, message, this.apiVersion.get());
+        }
+    }
+
+    public boolean apiVersionLt(String minVersion) {
+        String apiVersion = this.apiVersion == null ?
+                            null : this.apiVersion.get();
+        return apiVersion != null && !VersionUtil.gte(apiVersion, minVersion);
+    }
+
+    @Override
+    protected void checkStatus(Response response, Response.Status... statuses) {
+        boolean match = false;
+        for (Response.Status status : statuses) {
+            if (status.getStatusCode() == response.getStatus()) {
+                match = true;
+                break;
+            }
+        }
+        if (!match) {
+            throw ServerException.fromResponse(response);
+        }
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/driver/AuthManager.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/driver/AuthManager.java
new file mode 100644
index 0000000..356d1a4
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/driver/AuthManager.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.driver;
+
+import java.util.List;
+import java.util.Set;
+
+import org.apache.commons.collections.CollectionUtils;
+
+import com.baidu.hugegraph.api.auth.AccessAPI;
+import com.baidu.hugegraph.api.auth.BelongAPI;
+import com.baidu.hugegraph.api.auth.GroupAPI;
+import com.baidu.hugegraph.api.auth.LoginAPI;
+import com.baidu.hugegraph.api.auth.LogoutAPI;
+import com.baidu.hugegraph.api.auth.ProjectAPI;
+import com.baidu.hugegraph.api.auth.TargetAPI;
+import com.baidu.hugegraph.api.auth.TokenAPI;
+import com.baidu.hugegraph.api.auth.UserAPI;
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.structure.auth.Access;
+import com.baidu.hugegraph.structure.auth.Belong;
+import com.baidu.hugegraph.structure.auth.Group;
+import com.baidu.hugegraph.structure.auth.Login;
+import com.baidu.hugegraph.structure.auth.LoginResult;
+import com.baidu.hugegraph.structure.auth.Project;
+import com.baidu.hugegraph.structure.auth.Target;
+import com.baidu.hugegraph.structure.auth.TokenPayload;
+import com.baidu.hugegraph.structure.auth.User;
+import com.baidu.hugegraph.structure.auth.User.UserRole;
+
+public class AuthManager {
+
+    private final TargetAPI targetAPI;
+    private final GroupAPI groupAPI;
+    private final UserAPI userAPI;
+    private final AccessAPI accessAPI;
+    private final BelongAPI belongAPI;
+    private final ProjectAPI projectAPI;
+    private final LoginAPI loginAPI;
+    private final LogoutAPI logoutAPI;
+    private final TokenAPI tokenAPI;
+
+    public AuthManager(RestClient client, String graph) {
+        this.targetAPI = new TargetAPI(client, graph);
+        this.groupAPI = new GroupAPI(client, graph);
+        this.userAPI = new UserAPI(client, graph);
+        this.accessAPI = new AccessAPI(client, graph);
+        this.belongAPI = new BelongAPI(client, graph);
+        this.projectAPI = new ProjectAPI(client, graph);
+        this.loginAPI = new LoginAPI(client, graph);
+        this.logoutAPI = new LogoutAPI(client, graph);
+        this.tokenAPI = new TokenAPI(client, graph);
+    }
+
+    public List<Target> listTargets() {
+        return this.listTargets(-1);
+    }
+
+    public List<Target> listTargets(int limit) {
+        return this.targetAPI.list(limit);
+    }
+
+    public Target getTarget(Object id) {
+        return this.targetAPI.get(id);
+    }
+
+    public Target createTarget(Target target) {
+        return this.targetAPI.create(target);
+    }
+
+    public Target updateTarget(Target target) {
+        return this.targetAPI.update(target);
+    }
+
+    public void deleteTarget(Object id) {
+        this.targetAPI.delete(id);
+    }
+
+    public List<Group> listGroups() {
+        return this.listGroups(-1);
+    }
+
+    public List<Group> listGroups(int limit) {
+        return this.groupAPI.list(limit);
+    }
+
+    public Group getGroup(Object id) {
+        return this.groupAPI.get(id);
+    }
+
+    public Group createGroup(Group group) {
+        return this.groupAPI.create(group);
+    }
+
+    public Group updateGroup(Group group) {
+        return this.groupAPI.update(group);
+    }
+
+    public void deleteGroup(Object id) {
+        this.groupAPI.delete(id);
+    }
+
+    public List<User> listUsers() {
+        return this.listUsers(-1);
+    }
+
+    public List<User> listUsers(int limit) {
+        return this.userAPI.list(limit);
+    }
+
+    public User getUser(Object id) {
+        return this.userAPI.get(id);
+    }
+
+    public UserRole getUserRole(Object id) {
+        return this.userAPI.getUserRole(id);
+    }
+
+    public User createUser(User user) {
+        return this.userAPI.create(user);
+    }
+
+    public User updateUser(User user) {
+        return this.userAPI.update(user);
+    }
+
+    public void deleteUser(Object id) {
+        this.userAPI.delete(id);
+    }
+
+    public List<Access> listAccesses() {
+        return this.listAccesses(-1);
+    }
+
+    public List<Access> listAccesses(int limit) {
+        return this.accessAPI.list(null, null, limit);
+    }
+
+    public List<Access> listAccessesByGroup(Object group, int limit) {
+        return this.accessAPI.list(group, null, limit);
+    }
+
+    public List<Access> listAccessesByTarget(Object target, int limit) {
+        return this.accessAPI.list(null, target, limit);
+    }
+
+    public Access getAccess(Object id) {
+        return this.accessAPI.get(id);
+    }
+
+    public Access createAccess(Access access) {
+        return this.accessAPI.create(access);
+    }
+
+    public Access updateAccess(Access access) {
+        return this.accessAPI.update(access);
+    }
+
+    public void deleteAccess(Object id) {
+        this.accessAPI.delete(id);
+    }
+
+    public List<Belong> listBelongs() {
+        return this.listBelongs(-1);
+    }
+
+    public List<Belong> listBelongs(int limit) {
+        return this.belongAPI.list(null, null, limit);
+    }
+
+    public List<Belong> listBelongsByUser(Object user, int limit) {
+        return this.belongAPI.list(user, null, limit);
+    }
+
+    public List<Belong> listBelongsByGroup(Object group, int limit) {
+        return this.belongAPI.list(null, group, limit);
+    }
+
+    public Belong getBelong(Object id) {
+        return this.belongAPI.get(id);
+    }
+
+    public Belong createBelong(Belong belong) {
+        return this.belongAPI.create(belong);
+    }
+
+    public Belong updateBelong(Belong belong) {
+        return this.belongAPI.update(belong);
+    }
+
+    public void deleteBelong(Object id) {
+        this.belongAPI.delete(id);
+    }
+
+    public void deleteAll() {
+        for (Belong belong : this.listBelongs()) {
+            this.deleteBelong(belong.id());
+        }
+        for (Access access : this.listAccesses()) {
+            this.deleteAccess(access.id());
+        }
+
+        for (User user : this.listUsers()) {
+            if (user.name().equals("admin")) {
+                continue;
+            }
+            this.deleteUser(user.id());
+        }
+        for (Group group : this.listGroups()) {
+            this.deleteGroup(group.id());
+        }
+        for (Target target : this.listTargets()) {
+            this.deleteTarget(target.id());
+        }
+        for (Project project : this.listProjects()) {
+            Set<String> graphs = project.graphs();
+            if (CollectionUtils.isNotEmpty(graphs)) {
+                this.projectRemoveGraphs(project.id(), graphs);
+            }
+            this.deleteProject(project.id());
+        }
+    }
+
+    public Project createProject(Project project) {
+        return this.projectAPI.create(project);
+    }
+
+    public Project getProject(Object id) {
+        return this.projectAPI.get(id);
+    }
+
+    public List<Project> listProjects() {
+        return this.listProject(-1);
+    }
+
+    public List<Project> listProject(int limit) {
+        return this.projectAPI.list(limit);
+    }
+
+    public Project updateProject(Project project) {
+        return this.projectAPI.update(project);
+    }
+
+    public Project projectAddGraphs(Object projectId, Set<String> graphs) {
+        return this.projectAPI.addGraphs(projectId, graphs);
+    }
+
+    public Project projectRemoveGraphs(Object projectId, Set<String> graphs) {
+        return this.projectAPI.removeGraphs(projectId, graphs);
+    }
+
+    public void deleteProject(Object id) {
+        this.projectAPI.delete(id);
+    }
+
+    public LoginResult login(Login login) {
+        return this.loginAPI.login(login);
+    }
+
+    public void logout() {
+        this.logoutAPI.logout();
+    }
+
+    public TokenPayload verifyToken() {
+        return this.tokenAPI.verifyToken();
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/driver/GraphManager.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/driver/GraphManager.java
new file mode 100644
index 0000000..48350b3
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/driver/GraphManager.java
@@ -0,0 +1,489 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.driver;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import com.baidu.hugegraph.annotation.UnimplementedFeature;
+import com.baidu.hugegraph.api.graph.EdgeAPI;
+import com.baidu.hugegraph.api.graph.VertexAPI;
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.exception.InvalidOperationException;
+import com.baidu.hugegraph.structure.GraphElement;
+import com.baidu.hugegraph.structure.constant.Direction;
+import com.baidu.hugegraph.structure.constant.T;
+import com.baidu.hugegraph.structure.graph.BatchEdgeRequest;
+import com.baidu.hugegraph.structure.graph.BatchOlapPropertyRequest;
+import com.baidu.hugegraph.structure.graph.BatchVertexRequest;
+import com.baidu.hugegraph.structure.graph.Edge;
+import com.baidu.hugegraph.structure.graph.GraphIterator;
+import com.baidu.hugegraph.structure.graph.Vertex;
+import com.baidu.hugegraph.util.E;
+
+public class GraphManager {
+
+    private final String graph;
+    private final VertexAPI vertexAPI;
+    private final EdgeAPI edgeAPI;
+
+    public GraphManager(RestClient client, String graph) {
+        this.graph = graph;
+        this.vertexAPI = new VertexAPI(client, graph);
+        this.edgeAPI = new EdgeAPI(client, graph);
+    }
+
+    public String graph() {
+        return this.graph;
+    }
+
+    public Vertex addVertex(Vertex vertex) {
+        vertex = this.vertexAPI.create(vertex);
+        this.attachManager(vertex);
+        return vertex;
+    }
+
+    public Vertex addVertex(Object... keyValues) {
+        Object label = this.getValue(T.label, keyValues);
+        if (!(label instanceof String)) {
+            throw new IllegalArgumentException(String.format(
+                      "Expect a string value as the vertex label " +
+                      "argument, but got: %s", label));
+        }
+        Vertex vertex = new Vertex(String.valueOf(label));
+        vertex.id(this.getValue(T.id, keyValues));
+        this.attachProperties(vertex, keyValues);
+        return this.addVertex(vertex);
+    }
+
+    public Vertex addVertex(String label, Map<String, Object> properties) {
+        return this.addVertex(label, null, properties);
+    }
+
+    public Vertex addVertex(String label, Object id,
+                            Map<String, Object> properties) {
+        Vertex vertex = new Vertex(label);
+        vertex.id(id);
+        this.attachProperties(vertex, properties);
+        return this.addVertex(vertex);
+    }
+
+    public Vertex getVertex(Object vertexId) {
+        Vertex vertex = this.vertexAPI.get(vertexId);
+        this.attachManager(vertex);
+        return vertex;
+    }
+
+    public List<Vertex> addVertices(List<Vertex> vertices) {
+        List<Object> ids = this.vertexAPI.create(vertices);
+        for (int i = 0; i < vertices.size(); i++) {
+            Vertex vertex = vertices.get(i);
+            vertex.id(ids.get(i));
+            this.attachManager(vertex);
+        }
+        return vertices;
+    }
+
+    public List<Vertex> listVertices() {
+        return this.listVertices(-1);
+    }
+
+    public List<Vertex> listVertices(int limit) {
+        return this.listVertices(null, null, false, 0, limit);
+    }
+
+    public List<Vertex> listVertices(int offset, int limit) {
+        return this.listVertices(null, null, false, offset, limit);
+    }
+
+    public List<Vertex> listVertices(String label) {
+        return this.listVertices(label, null, false, 0, -1);
+    }
+
+    public List<Vertex> listVertices(String label, int limit) {
+        return this.listVertices(label, null, false, 0, limit);
+    }
+
+    public List<Vertex> listVertices(String label,
+                                     Map<String, Object> properties) {
+        return this.listVertices(label, properties, false, 0, -1);
+    }
+
+    public List<Vertex> listVertices(String label,
+                                     Map<String, Object> properties,
+                                     int limit) {
+        return this.listVertices(label, properties, false, 0, limit);
+    }
+
+    public List<Vertex> listVertices(String label,
+                                     Map<String, Object> properties,
+                                     boolean keepP) {
+        return this.listVertices(label, properties, keepP, 0, -1);
+    }
+
+    public List<Vertex> listVertices(String label,
+                                     Map<String, Object> properties,
+                                     boolean keepP,
+                                     int limit) {
+        return this.listVertices(label, properties, keepP, 0, limit);
+    }
+
+    public List<Vertex> listVertices(String label,
+                                     Map<String, Object> properties,
+                                     int offset,
+                                     int limit) {
+        return this.listVertices(label, properties, false, offset, limit);
+    }
+
+    public List<Vertex> listVertices(String label,
+                                     Map<String, Object> properties,
+                                     boolean keepP,
+                                     int offset,
+                                     int limit) {
+        List<Vertex> vertices = this.vertexAPI.list(label, properties, keepP,
+                                                    offset, null, limit)
+                                              .results();
+        for (Vertex vertex : vertices) {
+            this.attachManager(vertex);
+        }
+        return vertices;
+    }
+
+    public Iterator<Vertex> iterateVertices(int sizePerPage) {
+        return this.iterateVertices(null, null, sizePerPage);
+    }
+
+    public Iterator<Vertex> iterateVertices(String label, int sizePerPage) {
+        return this.iterateVertices(label, null, sizePerPage);
+    }
+
+    public Iterator<Vertex> iterateVertices(String label,
+                                            Map<String, Object> properties,
+                                            int sizePerPage) {
+        return new GraphIterator<>(this, sizePerPage, (page) -> {
+            return this.vertexAPI.list(label, properties, 0, page, sizePerPage);
+        });
+    }
+
+    public void removeVertex(Object vertexId) {
+        this.vertexAPI.delete(vertexId);
+    }
+
+    public List<Vertex> updateVertices(BatchVertexRequest request) {
+        List<Vertex> newVertices = this.vertexAPI.update(request);
+        newVertices.forEach(vertex -> this.attachManager(vertex));
+        return newVertices;
+    }
+
+    public int updateVertices(BatchOlapPropertyRequest request) {
+        return this.vertexAPI.update(request);
+    }
+
+    public Vertex appendVertexProperty(Vertex vertex) {
+        vertex = this.vertexAPI.append(vertex);
+        this.attachManager(vertex);
+        return vertex;
+    }
+
+    public Vertex eliminateVertexProperty(Vertex vertex) {
+        vertex = this.vertexAPI.eliminate(vertex);
+        this.attachManager(vertex);
+        return vertex;
+    }
+
+    public Edge addEdge(Edge edge) {
+        if (edge.id() != null) {
+            throw new InvalidOperationException(
+                      "Not allowed to custom id for edge: '%s'", edge);
+        }
+        edge = this.edgeAPI.create(edge);
+        this.attachManager(edge);
+        return edge;
+    }
+
+    public Edge addEdge(Vertex source, String label, Vertex target,
+                        Object... properties) {
+        return this.addEdge(source.id(), label, target.id(), properties);
+    }
+
+    public Edge addEdge(Object sourceId, String label, Object targetId,
+                        Object... properties) {
+        Edge edge = new Edge(label);
+        edge.sourceId(sourceId);
+        edge.targetId(targetId);
+        this.attachProperties(edge, properties);
+        return this.addEdge(edge);
+    }
+
+    public Edge addEdge(Vertex source, String label, Vertex target,
+                        Map<String, Object> properties) {
+        return this.addEdge(source.id(), label, target.id(), properties);
+    }
+
+    public Edge addEdge(Object sourceId, String label, Object targetId,
+                        Map<String, Object> properties) {
+        Edge edge = new Edge(label);
+        edge.sourceId(sourceId);
+        edge.targetId(targetId);
+        this.attachProperties(edge, properties);
+        return this.addEdge(edge);
+    }
+
+    public Edge getEdge(String edgeId) {
+        Edge edge = this.edgeAPI.get(edgeId);
+        this.attachManager(edge);
+        return edge;
+    }
+
+    public List<Edge> addEdges(List<Edge> edges) {
+        return this.addEdges(edges, true);
+    }
+
+    public List<Edge> addEdges(List<Edge> edges, boolean checkVertex) {
+        for (Edge edge : edges) {
+            edge.sourceId();
+            edge.targetId();
+        }
+        List<String> ids = this.edgeAPI.create(edges, checkVertex);
+        for (int i = 0; i < edges.size(); i++) {
+            Edge edge = edges.get(i);
+            edge.id(ids.get(i));
+            this.attachManager(edge);
+        }
+        return edges;
+    }
+
+    public List<Edge> listEdges() {
+        return this.listEdges(-1);
+    }
+
+    public List<Edge> listEdges(int limit) {
+        return this.getEdges(null, null, null, null, 0, limit);
+    }
+
+    public List<Edge> listEdges(int offset, int limit) {
+        return this.getEdges(null, null, null, null, offset, limit);
+    }
+
+    public List<Edge> listEdges(String label) {
+        return this.getEdges(null, null, label, null, 0, -1);
+    }
+
+    public List<Edge> listEdges(String label, int limit) {
+        return this.getEdges(null, null, label, null, 0, limit);
+    }
+
+    public List<Edge> listEdges(String label,
+                                Map<String, Object> properties) {
+        return this.getEdges(null, null, label, properties, 0, -1);
+    }
+
+    public List<Edge> listEdges(String label,
+                                Map<String, Object> properties,
+                                int limit) {
+        return this.getEdges(null, null, label, properties, 0, limit);
+    }
+
+    public List<Edge> listEdges(String label,
+                                Map<String, Object> properties,
+                                boolean keepP) {
+        return this.getEdges(null, null, label, properties, keepP, 0, -1);
+    }
+
+    public List<Edge> listEdges(String label,
+                                Map<String, Object> properties,
+                                boolean keepP,
+                                int limit) {
+        return this.getEdges(null, null, label, properties, keepP, 0, limit);
+    }
+
+    public List<Edge> getEdges(Object vertexId) {
+        return this.getEdges(vertexId, Direction.BOTH, null, null, 0, -1);
+    }
+
+    public List<Edge> getEdges(Object vertexId, int limit) {
+        return this.getEdges(vertexId, Direction.BOTH, null, null, 0, limit);
+    }
+
+    public List<Edge> getEdges(Object vertexId, Direction direction) {
+        return this.getEdges(vertexId, direction, null, null, 0, -1);
+    }
+
+    public List<Edge> getEdges(Object vertexId,
+                               Direction direction,
+                               int limit) {
+        return this.getEdges(vertexId, direction, null, null, 0, limit);
+    }
+
+    public List<Edge> getEdges(Object vertexId,
+                               Direction direction,
+                               String label) {
+        return this.getEdges(vertexId, direction, label, null, 0, -1);
+    }
+
+    public List<Edge> getEdges(Object vertexId,
+                               Direction direction,
+                               String label,
+                               int limit) {
+        return this.getEdges(vertexId, direction, label, null, 0, limit);
+    }
+
+    public List<Edge> getEdges(Object vertexId,
+                               Direction direction,
+                               String label,
+                               Map<String, Object> properties) {
+        return this.getEdges(vertexId, direction, label, properties, 0, -1);
+    }
+
+    public List<Edge> getEdges(Object vertexId,
+                               Direction direction,
+                               String label,
+                               Map<String, Object> properties,
+                               int offset,
+                               int limit) {
+        return this.getEdges(vertexId, direction, label, properties, false,
+                             offset, limit);
+    }
+
+    public List<Edge> getEdges(Object vertexId,
+                               Direction direction,
+                               String label,
+                               Map<String, Object> properties,
+                               boolean keepP,
+                               int offset,
+                               int limit) {
+        List<Edge> edges = this.edgeAPI.list(vertexId, direction, label,
+                                             properties, keepP,
+                                             offset, null, limit)
+                                       .results();
+        for (Edge edge : edges) {
+            this.attachManager(edge);
+        }
+        return edges;
+    }
+
+    public Iterator<Edge> iterateEdges(int sizePerPage) {
+        return this.iterateEdges(null, (Map<String, Object>) null, sizePerPage);
+    }
+
+    @UnimplementedFeature(desc = "Server doesn't support paging by label")
+    public Iterator<Edge> iterateEdges(String label, int sizePerPage) {
+        return this.iterateEdges(label, (Map<String, Object>) null, sizePerPage);
+    }
+
+    @UnimplementedFeature(desc = "Server doesn't support paging by label and properties")
+    public Iterator<Edge> iterateEdges(String label,
+                                       Map<String, Object> properties,
+                                       int sizePerPage) {
+        return new GraphIterator<>(this, sizePerPage, (page) -> {
+            return this.edgeAPI.list(null, null, label, properties,
+                                     0, page, sizePerPage);
+        });
+    }
+
+    public Iterator<Edge> iterateEdges(Object vertexId, int sizePerPage) {
+        return this.iterateEdges(vertexId, Direction.BOTH, null, null,
+                                 sizePerPage);
+    }
+
+    public Iterator<Edge> iterateEdges(Object vertexId,
+                                       Direction direction,
+                                       int sizePerPage) {
+        return this.iterateEdges(vertexId, direction, null, null, sizePerPage);
+    }
+
+    public Iterator<Edge> iterateEdges(Object vertexId,
+                                       Direction direction,
+                                       String label,
+                                       int sizePerPage) {
+        return this.iterateEdges(vertexId, direction, label, null, sizePerPage);
+    }
+
+    public Iterator<Edge> iterateEdges(Object vertexId,
+                                       Direction direction,
+                                       String label,
+                                       Map<String, Object> properties,
+                                       int sizePerPage) {
+        return new GraphIterator<>(this, sizePerPage, (page) -> {
+            return this.edgeAPI.list(vertexId, direction, label, properties,
+                                     0, page, sizePerPage);
+        });
+    }
+
+    public void removeEdge(String edgeId) {
+        this.edgeAPI.delete(edgeId);
+    }
+
+    public List<Edge> updateEdges(BatchEdgeRequest request) {
+        List<Edge> newEdges = this.edgeAPI.update(request);
+        newEdges.forEach(edge -> this.attachManager(edge));
+        return newEdges;
+    }
+
+    public Edge appendEdgeProperty(Edge edge) {
+        edge = this.edgeAPI.append(edge);
+        this.attachManager(edge);
+        return edge;
+    }
+
+    public Edge eliminateEdgeProperty(Edge edge) {
+        edge = this.edgeAPI.eliminate(edge);
+        this.attachManager(edge);
+        return edge;
+    }
+
+    private Object getValue(String key, Object... keyValues) {
+        E.checkArgument((keyValues.length & 0x01) == 0,
+                        "The number of parameters must be even");
+        Object value = null;
+        for (int i = 0; i < keyValues.length; i = i + 2) {
+            if (keyValues[i].equals(key)) {
+                value = keyValues[i + 1];
+                break;
+            }
+        }
+        return value;
+    }
+
+    private void attachProperties(GraphElement element, Object... properties) {
+        E.checkArgument((properties.length & 0x01) == 0,
+                        "The number of properties must be even");
+        for (int i = 0; i < properties.length; i = i + 2) {
+            if (!properties[i].equals(T.id) &&
+                !properties[i].equals(T.label)) {
+                element.property((String) properties[i], properties[i + 1]);
+            }
+        }
+    }
+
+    private void attachProperties(GraphElement element,
+                                  Map<String, Object> properties) {
+        if (properties != null) {
+            for (Map.Entry<String, Object> entry : properties.entrySet()) {
+                element.property(entry.getKey(), entry.getValue());
+            }
+        }
+    }
+
+    private void attachManager(GraphElement element) {
+        element.attachManager(this);
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/driver/GraphsManager.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/driver/GraphsManager.java
new file mode 100644
index 0000000..bbb933a
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/driver/GraphsManager.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.driver;
+
+import java.util.List;
+import java.util.Map;
+
+import com.baidu.hugegraph.api.graphs.GraphsAPI;
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.structure.constant.GraphMode;
+import com.baidu.hugegraph.structure.constant.GraphReadMode;
+
+public class GraphsManager {
+
+    private GraphsAPI graphsAPI;
+
+    public GraphsManager(RestClient client) {
+        this.graphsAPI = new GraphsAPI(client);
+    }
+
+    public Map<String, String> createGraph(String name, String configText) {
+        return this.graphsAPI.create(name, null, configText);
+    }
+
+    public Map<String, String> cloneGraph(String name, String cloneGraphName) {
+        return this.graphsAPI.create(name, cloneGraphName, null);
+    }
+
+    public Map<String, String> cloneGraph(String name, String cloneGraphName,
+                                          String configText) {
+        return this.graphsAPI.create(name, cloneGraphName, configText);
+    }
+
+    public Map<String, String> getGraph(String graph) {
+        return this.graphsAPI.get(graph);
+    }
+
+    public List<String> listGraph() {
+        return this.graphsAPI.list();
+    }
+
+    public void clearGraph(String graph, String message) {
+        this.graphsAPI.clear(graph, message);
+    }
+
+    public void dropGraph(String graph, String message) {
+        this.graphsAPI.drop(graph, message);
+    }
+
+    public void mode(String graph, GraphMode mode) {
+        this.graphsAPI.mode(graph, mode);
+    }
+
+    public GraphMode mode(String graph) {
+        return this.graphsAPI.mode(graph);
+    }
+
+    public void readMode(String graph, GraphReadMode readMode) {
+        this.graphsAPI.readMode(graph, readMode);
+    }
+
+    public GraphReadMode readMode(String graph) {
+        return this.graphsAPI.readMode(graph);
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/driver/GremlinManager.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/driver/GremlinManager.java
new file mode 100644
index 0000000..e52d1ae
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/driver/GremlinManager.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.driver;
+
+import com.baidu.hugegraph.api.gremlin.GremlinAPI;
+import com.baidu.hugegraph.api.gremlin.GremlinRequest;
+import com.baidu.hugegraph.api.job.GremlinJobAPI;
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.structure.gremlin.Response;
+import com.baidu.hugegraph.structure.gremlin.ResultSet;
+
+public class GremlinManager {
+
+    private final GraphManager graphManager;
+
+    private GremlinAPI gremlinAPI;
+    private GremlinJobAPI gremlinJobAPI;
+    private String graph;
+
+    public GremlinManager(RestClient client, String graph,
+                          GraphManager graphManager) {
+        this.graphManager = graphManager;
+        this.gremlinAPI = new GremlinAPI(client);
+        this.gremlinJobAPI = new GremlinJobAPI(client, graph);
+        this.graph = graph;
+    }
+
+    public ResultSet execute(GremlinRequest request) {
+        // Bind "graph" to all graphs
+        request.aliases.put("graph", this.graph);
+        // Bind "g" to all graphs by custom rule which define in gremlin server.
+        request.aliases.put("g", "__g_" + this.graph);
+
+        Response response = this.gremlinAPI.post(request);
+        response.graphManager(this.graphManager);
+        // TODO: Can add some checks later
+        return response.result();
+    }
+
+    public long executeAsTask(GremlinRequest request) {
+        return this.gremlinJobAPI.execute(request);
+    }
+
+    public GremlinRequest.Builder gremlin(String gremlin) {
+        return new GremlinRequest.Builder(gremlin, this);
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/driver/HugeClient.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/driver/HugeClient.java
new file mode 100644
index 0000000..43e5f67
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/driver/HugeClient.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.driver;
+
+import java.io.Closeable;
+
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.rest.ClientException;
+import com.baidu.hugegraph.util.VersionUtil;
+import com.baidu.hugegraph.version.ClientVersion;
+import jakarta.ws.rs.ProcessingException;
+
+public class HugeClient implements Closeable {
+
+    static {
+        ClientVersion.check();
+    }
+    private final RestClient client;
+    private final boolean borrowedClient;
+
+    private VersionManager version;
+    private GraphsManager graphs;
+    private SchemaManager schema;
+    private GraphManager graph;
+    private GremlinManager gremlin;
+    private TraverserManager traverser;
+    private VariablesManager variable;
+    private JobManager job;
+    private TaskManager task;
+    private AuthManager auth;
+    private MetricsManager metrics;
+
+    public HugeClient(HugeClientBuilder builder) {
+        this.borrowedClient = false;
+        try {
+            this.client = new RestClient(builder.url(),
+                                         builder.username(),
+                                         builder.password(),
+                                         builder.timeout(),
+                                         builder.maxConns(),
+                                         builder.maxConnsPerRoute(),
+                                         builder.trustStoreFile(),
+                                         builder.trustStorePassword());
+        } catch (ProcessingException e) {
+            throw new ClientException("Failed to connect url '%s'",
+                                      builder.url());
+        }
+        try {
+            this.initManagers(this.client, builder.graph());
+        } catch (Throwable e) {
+            this.client.close();
+            throw e;
+        }
+    }
+
+    public HugeClient(HugeClient client, String graph) {
+        this.borrowedClient = true;
+        this.client = client.client;
+        this.initManagers(this.client, graph);
+    }
+
+    public static HugeClientBuilder builder(String url, String graph) {
+        return new HugeClientBuilder(url, graph);
+    }
+
+    @Override
+    public void close() {
+        if (!this.borrowedClient) {
+            this.client.close();
+        }
+    }
+
+    private void initManagers(RestClient client, String graph) {
+        assert client != null;
+        // Check hugegraph-server api version
+        this.version = new VersionManager(client);
+        this.checkServerApiVersion();
+
+        this.graphs = new GraphsManager(client);
+        this.schema = new SchemaManager(client, graph);
+        this.graph = new GraphManager(client, graph);
+        this.gremlin = new GremlinManager(client, graph, this.graph);
+        this.traverser = new TraverserManager(client, this.graph);
+        this.variable = new VariablesManager(client, graph);
+        this.job = new JobManager(client, graph);
+        this.task = new TaskManager(client, graph);
+        this.auth = new AuthManager(client, graph);
+        this.metrics = new MetricsManager(client);
+    }
+
+    private void checkServerApiVersion() {
+        VersionUtil.Version apiVersion = VersionUtil.Version.of(
+                                         this.version.getApiVersion());
+        VersionUtil.check(apiVersion, "0.38", "0.68",
+                          "hugegraph-api in server");
+        this.client.apiVersion(apiVersion);
+    }
+
+    public GraphsManager graphs() {
+        return this.graphs;
+    }
+
+    public SchemaManager schema() {
+        return this.schema;
+    }
+
+    public GraphManager graph() {
+        return this.graph;
+    }
+
+    public GremlinManager gremlin() {
+        return this.gremlin;
+    }
+
+    public TraverserManager traverser() {
+        return this.traverser;
+    }
+
+    public VariablesManager variables() {
+        return this.variable;
+    }
+
+    public JobManager job() {
+        return this.job;
+    }
+
+    public TaskManager task() {
+        return this.task;
+    }
+
+    public AuthManager auth() {
+        return this.auth;
+    }
+
+    public MetricsManager metrics() {
+        return this.metrics;
+    }
+
+    public void setAuthContext(String auth) {
+        this.client.setAuthContext(auth);
+    }
+
+    public String getAuthContext() {
+        return this.client.getAuthContext();
+    }
+
+    public void resetAuthContext() {
+        this.client.resetAuthContext();
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/driver/HugeClientBuilder.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/driver/HugeClientBuilder.java
new file mode 100644
index 0000000..4eedeae
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/driver/HugeClientBuilder.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.driver;
+
+import com.baidu.hugegraph.util.E;
+
+public class HugeClientBuilder {
+
+    private static final int CPUS = Runtime.getRuntime().availableProcessors();
+    private static final int DEFAULT_TIMEOUT = 20;
+    private static final int DEFAULT_MAX_CONNS = 4 * CPUS;
+    private static final int DEFAULT_MAX_CONNS_PER_ROUTE = 2 * CPUS;
+    private static final int DEFAULT_IDLE_TIME = 30;
+
+    private String url;
+    private String graph;
+    private String username;
+    private String password;
+    private int timeout;
+    private int maxConns;
+    private int maxConnsPerRoute;
+    private int idleTime;
+    private String trustStoreFile;
+    private String trustStorePassword;
+
+    public HugeClientBuilder(String url, String graph) {
+        E.checkArgument(url != null && !url.isEmpty(),
+                        "Expect a string value as the url " +
+                        "parameter argument, but got: %s", url);
+        E.checkArgument(graph != null && !graph.isEmpty(),
+                        "Expect a string value as the graph name " +
+                        "parameter argument, but got: %s", graph);
+        this.url = url;
+        this.graph = graph;
+        this.username = "";
+        this.password = "";
+        this.timeout = DEFAULT_TIMEOUT;
+        this.maxConns = DEFAULT_MAX_CONNS;
+        this.maxConnsPerRoute = DEFAULT_MAX_CONNS_PER_ROUTE;
+        this.trustStoreFile = "";
+        this.trustStorePassword = "";
+        this.idleTime = DEFAULT_IDLE_TIME;
+    }
+
+    public HugeClient build() {
+        E.checkArgument(this.url != null,
+                        "The url parameter can't be null");
+        E.checkArgument(this.graph != null,
+                        "The graph parameter can't be null");
+        return new HugeClient(this);
+    }
+
+    public HugeClientBuilder configGraph(String graph) {
+        this.graph = graph;
+        return this;
+    }
+
+    public HugeClientBuilder configIdleTime(int idleTime) {
+        E.checkArgument(idleTime > 0,
+                        "The idleTime parameter must be > 0, " +
+                        "but got %s", idleTime);
+        this.idleTime = idleTime;
+        return this;
+    }
+
+    public HugeClientBuilder configPool(int maxConns, int maxConnsPerRoute) {
+        if (maxConns == 0) {
+            maxConns = DEFAULT_MAX_CONNS;
+        }
+        if (maxConnsPerRoute == 0) {
+            maxConnsPerRoute = DEFAULT_MAX_CONNS_PER_ROUTE;
+        }
+        this.maxConns = maxConns;
+        this.maxConnsPerRoute = maxConnsPerRoute;
+        return this;
+    }
+
+    public HugeClientBuilder configSSL(String trustStoreFile,
+                                       String trustStorePassword) {
+        this.trustStoreFile = trustStoreFile;
+        this.trustStorePassword = trustStorePassword;
+        return this;
+    }
+
+    public HugeClientBuilder configTimeout(int timeout) {
+        if (timeout == 0) {
+            timeout = DEFAULT_TIMEOUT;
+        }
+        this.timeout = timeout;
+        return this;
+    }
+
+    public HugeClientBuilder configUrl(String url) {
+        this.url = url;
+        return this;
+    }
+
+    public HugeClientBuilder configUser(String username, String password) {
+        if (username == null) {
+            username = "";
+        }
+        if (password == null) {
+            password = "";
+        }
+        this.username = username;
+        this.password = password;
+
+        return this;
+    }
+
+    public String url() {
+        return this.url;
+    }
+
+    public String graph() {
+        return this.graph;
+    }
+
+    public String username() {
+        return this.username;
+    }
+
+    public String password() {
+        return this.password;
+    }
+
+    public int timeout() {
+        return this.timeout;
+    }
+
+    public int maxConns() {
+        return maxConns;
+    }
+
+    public int maxConnsPerRoute() {
+        return this.maxConnsPerRoute;
+    }
+
+    public int idleTime() {
+        return this.idleTime;
+    }
+
+    public String trustStoreFile() {
+        return this.trustStoreFile;
+    }
+
+    public String trustStorePassword() {
+        return this.trustStorePassword;
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/driver/JobManager.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/driver/JobManager.java
new file mode 100644
index 0000000..7fb440c
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/driver/JobManager.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.driver;
+
+import com.baidu.hugegraph.api.job.RebuildAPI;
+import com.baidu.hugegraph.api.task.TaskAPI;
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.structure.schema.EdgeLabel;
+import com.baidu.hugegraph.structure.schema.IndexLabel;
+import com.baidu.hugegraph.structure.schema.VertexLabel;
+
+import static com.baidu.hugegraph.api.task.TaskAPI.TASK_TIMEOUT;
+
+public class JobManager {
+
+    private RebuildAPI rebuildAPI;
+    private TaskAPI taskAPI;
+
+    public JobManager(RestClient client, String graph) {
+        this.rebuildAPI = new RebuildAPI(client, graph);
+        this.taskAPI = new TaskAPI(client, graph);
+    }
+
+    public void rebuild(VertexLabel vertexLabel) {
+        this.rebuild(vertexLabel, TASK_TIMEOUT);
+    }
+
+    public void rebuild(VertexLabel vertexLabel, long seconds) {
+        long task = this.rebuildAPI.rebuild(vertexLabel);
+        this.taskAPI.waitUntilTaskSuccess(task, seconds);
+    }
+
+    public long rebuildAsync(VertexLabel vertexLabel) {
+        return this.rebuildAPI.rebuild(vertexLabel);
+    }
+
+    public void rebuild(EdgeLabel edgeLabel) {
+        this.rebuild(edgeLabel, TASK_TIMEOUT);
+    }
+
+    public void rebuild(EdgeLabel edgeLabel, long seconds) {
+        long task = this.rebuildAPI.rebuild(edgeLabel);
+        this.taskAPI.waitUntilTaskSuccess(task, seconds);
+    }
+
+    public long rebuildAsync(EdgeLabel edgeLabel) {
+        return this.rebuildAPI.rebuild(edgeLabel);
+    }
+
+    public void rebuild(IndexLabel indexLabel) {
+        this.rebuild(indexLabel, TASK_TIMEOUT);
+    }
+
+    public void rebuild(IndexLabel indexLabel, long seconds) {
+        long task = this.rebuildAPI.rebuild(indexLabel);
+        this.taskAPI.waitUntilTaskSuccess(task, seconds);
+    }
+
+    public long rebuildAsync(IndexLabel indexLabel) {
+        return this.rebuildAPI.rebuild(indexLabel);
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/driver/MetricsManager.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/driver/MetricsManager.java
new file mode 100644
index 0000000..474fa2d
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/driver/MetricsManager.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.driver;
+
+import java.util.Map;
+
+import com.baidu.hugegraph.api.metrics.MetricsAPI;
+import com.baidu.hugegraph.client.RestClient;
+
+public class MetricsManager {
+
+    private MetricsAPI metricsAPI;
+
+    public MetricsManager(RestClient client) {
+        this.metricsAPI = new MetricsAPI(client);
+    }
+
+    public Map<String, Map<String, Object>> backend() {
+        return this.metricsAPI.backend();
+    }
+
+    public Map<String, Object> backend(String graph) {
+        return this.metricsAPI.backend(graph);
+    }
+
+    public Map<String, Map<String, Object>> system() {
+        return this.metricsAPI.system();
+    }
+
+    /**
+     * The nesting level is too deep, may need to optimize the server first
+     */
+    public Map<String, Map<String, Object>> all() {
+        return this.metricsAPI.all();
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/driver/SchemaManager.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/driver/SchemaManager.java
new file mode 100644
index 0000000..cee6f5d
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/driver/SchemaManager.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.driver;
+
+import static com.baidu.hugegraph.api.task.TaskAPI.TASK_TIMEOUT;
+
+import java.util.List;
+import java.util.Map;
+
+import com.baidu.hugegraph.api.schema.EdgeLabelAPI;
+import com.baidu.hugegraph.api.schema.IndexLabelAPI;
+import com.baidu.hugegraph.api.schema.PropertyKeyAPI;
+import com.baidu.hugegraph.api.schema.SchemaAPI;
+import com.baidu.hugegraph.api.schema.VertexLabelAPI;
+import com.baidu.hugegraph.api.task.TaskAPI;
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.structure.SchemaElement;
+import com.baidu.hugegraph.structure.schema.BuilderProxy;
+import com.baidu.hugegraph.structure.schema.EdgeLabel;
+import com.baidu.hugegraph.structure.schema.IndexLabel;
+import com.baidu.hugegraph.structure.schema.PropertyKey;
+import com.baidu.hugegraph.structure.schema.VertexLabel;
+
+public class SchemaManager {
+
+    private PropertyKeyAPI propertyKeyAPI;
+    private VertexLabelAPI vertexLabelAPI;
+    private EdgeLabelAPI edgeLabelAPI;
+    private IndexLabelAPI indexLabelAPI;
+    private SchemaAPI schemaAPI;
+    private TaskAPI taskAPI;
+
+    public SchemaManager(RestClient client, String graph) {
+        this.propertyKeyAPI = new PropertyKeyAPI(client, graph);
+        this.vertexLabelAPI = new VertexLabelAPI(client, graph);
+        this.edgeLabelAPI = new EdgeLabelAPI(client, graph);
+        this.indexLabelAPI = new IndexLabelAPI(client, graph);
+        this.schemaAPI = new SchemaAPI(client, graph);
+        this.taskAPI = new TaskAPI(client, graph);
+    }
+
+    public PropertyKey.Builder propertyKey(String name) {
+        PropertyKey.Builder builder = new PropertyKey.BuilderImpl(name, this);
+        BuilderProxy<PropertyKey.Builder> proxy = new BuilderProxy<>(builder);
+        return proxy.proxy();
+    }
+
+    public VertexLabel.Builder vertexLabel(String name) {
+        VertexLabel.Builder builder = new VertexLabel.BuilderImpl(name, this);
+        BuilderProxy<VertexLabel.Builder> proxy = new BuilderProxy<>(builder);
+        return proxy.proxy();
+    }
+
+    public EdgeLabel.Builder edgeLabel(String name) {
+        EdgeLabel.Builder builder = new EdgeLabel.BuilderImpl(name, this);
+        BuilderProxy<EdgeLabel.Builder> proxy = new BuilderProxy<>(builder);
+        return proxy.proxy();
+    }
+
+    public IndexLabel.Builder indexLabel(String name) {
+        IndexLabel.Builder builder = new IndexLabel.BuilderImpl(name, this);
+        BuilderProxy<IndexLabel.Builder> proxy = new BuilderProxy<>(builder);
+        return proxy.proxy();
+    }
+
+    public PropertyKey addPropertyKey(PropertyKey propertyKey) {
+        return this.addPropertyKey(propertyKey, TASK_TIMEOUT);
+    }
+
+    public PropertyKey addPropertyKey(PropertyKey propertyKey, long seconds) {
+        PropertyKey.PropertyKeyWithTask task = this.propertyKeyAPI
+                                                   .create(propertyKey);
+        if (task.taskId() != 0L) {
+            this.taskAPI.waitUntilTaskSuccess(task.taskId(), seconds);
+        }
+        return task.propertyKey();
+    }
+
+    public long addPropertyKeyAsync(PropertyKey propertyKey) {
+        PropertyKey.PropertyKeyWithTask task = this.propertyKeyAPI
+                                                   .create(propertyKey);
+        return task.taskId();
+    }
+
+    public PropertyKey appendPropertyKey(PropertyKey propertyKey) {
+        return this.propertyKeyAPI.append(propertyKey).propertyKey();
+    }
+
+    public PropertyKey eliminatePropertyKey(PropertyKey propertyKey) {
+        return this.propertyKeyAPI.eliminate(propertyKey).propertyKey();
+    }
+
+    public PropertyKey clearPropertyKey(PropertyKey propertyKey) {
+        return this.clearPropertyKey(propertyKey, TASK_TIMEOUT);
+    }
+
+    public PropertyKey clearPropertyKey(PropertyKey propertyKey, long seconds) {
+        PropertyKey.PropertyKeyWithTask task = this.propertyKeyAPI
+                                                   .clear(propertyKey);
+        if (task.taskId() != 0L) {
+            this.taskAPI.waitUntilTaskSuccess(task.taskId(), seconds);
+        }
+        return task.propertyKey();
+    }
+
+    public long clearPropertyKeyAsync(PropertyKey propertyKey) {
+        PropertyKey.PropertyKeyWithTask task = this.propertyKeyAPI
+                                                   .clear(propertyKey);
+        return task.taskId();
+    }
+
+    public void removePropertyKey(String name) {
+        this.removePropertyKey(name, TASK_TIMEOUT);
+    }
+
+    public void removePropertyKey(String name, long seconds) {
+        long task = this.propertyKeyAPI.delete(name);
+        this.taskAPI.waitUntilTaskSuccess(task, seconds);
+    }
+
+    public long removePropertyKeyAsync(String name) {
+        return this.propertyKeyAPI.delete(name);
+    }
+
+    public PropertyKey getPropertyKey(String name) {
+        return this.propertyKeyAPI.get(name);
+    }
+
+    public List<PropertyKey> getPropertyKeys() {
+        return this.propertyKeyAPI.list();
+    }
+
+    public List<PropertyKey> getPropertyKeys(List<String> names) {
+        return this.propertyKeyAPI.list(names);
+    }
+
+    public VertexLabel addVertexLabel(VertexLabel vertexLabel) {
+        return this.vertexLabelAPI.create(vertexLabel);
+    }
+
+    public VertexLabel appendVertexLabel(VertexLabel vertexLabel) {
+        return this.vertexLabelAPI.append(vertexLabel);
+    }
+
+    public VertexLabel eliminateVertexLabel(VertexLabel vertexLabel) {
+        return this.vertexLabelAPI.eliminate(vertexLabel);
+    }
+
+    public void removeVertexLabel(String name) {
+        long task = this.vertexLabelAPI.delete(name);
+        this.taskAPI.waitUntilTaskSuccess(task, TASK_TIMEOUT);
+    }
+
+    public void removeVertexLabel(String name, long seconds) {
+        long task = this.vertexLabelAPI.delete(name);
+        this.taskAPI.waitUntilTaskSuccess(task, seconds);
+    }
+
+    public long removeVertexLabelAsync(String name) {
+        return this.vertexLabelAPI.delete(name);
+    }
+
+    public VertexLabel getVertexLabel(String name) {
+        return this.vertexLabelAPI.get(name);
+    }
+
+    public List<VertexLabel> getVertexLabels() {
+        return this.vertexLabelAPI.list();
+    }
+
+    public List<VertexLabel> getVertexLabels(List<String> names) {
+        return this.vertexLabelAPI.list(names);
+    }
+
+    public EdgeLabel addEdgeLabel(EdgeLabel edgeLabel) {
+        return this.edgeLabelAPI.create(edgeLabel);
+    }
+
+    public EdgeLabel appendEdgeLabel(EdgeLabel edgeLabel) {
+        return this.edgeLabelAPI.append(edgeLabel);
+    }
+
+    public EdgeLabel eliminateEdgeLabel(EdgeLabel edgeLabel) {
+        return this.edgeLabelAPI.eliminate(edgeLabel);
+    }
+
+    public void removeEdgeLabel(String name) {
+        this.removeEdgeLabel(name, TASK_TIMEOUT);
+    }
+
+    public void removeEdgeLabel(String name, long seconds) {
+        long task = this.edgeLabelAPI.delete(name);
+        this.taskAPI.waitUntilTaskSuccess(task, seconds);
+    }
+
+    public long removeEdgeLabelAsync(String name) {
+        return this.edgeLabelAPI.delete(name);
+    }
+
+    public EdgeLabel getEdgeLabel(String name) {
+        return this.edgeLabelAPI.get(name);
+    }
+
+    public List<EdgeLabel> getEdgeLabels() {
+        return this.edgeLabelAPI.list();
+    }
+
+    public List<EdgeLabel> getEdgeLabels(List<String> names) {
+        return this.edgeLabelAPI.list(names);
+    }
+
+    public IndexLabel addIndexLabel(IndexLabel indexLabel) {
+        return this.addIndexLabel(indexLabel, TASK_TIMEOUT);
+    }
+
+    public IndexLabel addIndexLabel(IndexLabel indexLabel, long seconds) {
+        IndexLabel.IndexLabelWithTask cil = this.indexLabelAPI
+                                                .create(indexLabel);
+        if (cil.taskId() != 0L) {
+            this.taskAPI.waitUntilTaskSuccess(cil.taskId(), seconds);
+        }
+        return cil.indexLabel();
+    }
+
+    public long addIndexLabelAsync(IndexLabel indexLabel) {
+        IndexLabel.IndexLabelWithTask cil = this.indexLabelAPI
+                                                .create(indexLabel);
+        return cil.taskId();
+    }
+
+    public IndexLabel appendIndexLabel(IndexLabel indexLabel) {
+        return this.indexLabelAPI.append(indexLabel);
+    }
+
+    public IndexLabel eliminateIndexLabel(IndexLabel indexLabel) {
+        return this.indexLabelAPI.eliminate(indexLabel);
+    }
+
+    public void removeIndexLabel(String name) {
+        this.removeIndexLabel(name, TASK_TIMEOUT);
+    }
+
+    public void removeIndexLabel(String name, long secondss) {
+        long task = this.indexLabelAPI.delete(name);
+        this.taskAPI.waitUntilTaskSuccess(task, secondss);
+    }
+
+    public long removeIndexLabelAsync(String name) {
+        return this.indexLabelAPI.delete(name);
+    }
+
+    public IndexLabel getIndexLabel(String name) {
+        return this.indexLabelAPI.get(name);
+    }
+
+    public List<IndexLabel> getIndexLabels() {
+        return this.indexLabelAPI.list();
+    }
+
+    public List<IndexLabel> getIndexLabels(List<String> names) {
+        return this.indexLabelAPI.list(names);
+    }
+
+    public Map<String, List<SchemaElement>> getSchema() {
+        return this.schemaAPI.list();
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/driver/TaskManager.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/driver/TaskManager.java
new file mode 100644
index 0000000..982bd77
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/driver/TaskManager.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.driver;
+
+import java.util.List;
+
+import com.baidu.hugegraph.api.task.TaskAPI;
+import com.baidu.hugegraph.api.task.TasksWithPage;
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.structure.Task;
+
+public class TaskManager {
+
+    private TaskAPI taskAPI;
+
+    public TaskManager(RestClient client, String graph) {
+        this.taskAPI = new TaskAPI(client, graph);
+    }
+
+    public List<Task> list() {
+        return this.list(-1);
+    }
+
+    public List<Task> list(long limit) {
+        return this.taskAPI.list(null, limit);
+    }
+
+    public List<Task> list(List<Long> ids) {
+        return this.taskAPI.list(ids);
+    }
+
+    public List<Task> list(String status) {
+        return this.list(status, -1L);
+    }
+
+    public List<Task> list(String status, long limit) {
+        return this.taskAPI.list(status, limit);
+    }
+
+    public TasksWithPage list(String status, String page, long limit) {
+        return this.taskAPI.list(status, page, limit);
+    }
+
+    public Task get(long id) {
+        return this.taskAPI.get(id);
+    }
+
+    public void delete(long id) {
+        this.taskAPI.delete(id);
+    }
+
+    public Task cancel(long id) {
+        return this.taskAPI.cancel(id);
+    }
+
+    public Task waitUntilTaskCompleted(long taskId, long seconds) {
+        return this.taskAPI.waitUntilTaskSuccess(taskId, seconds);
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/driver/TraverserManager.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/driver/TraverserManager.java
new file mode 100644
index 0000000..f8ba4de
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/driver/TraverserManager.java
@@ -0,0 +1,558 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.driver;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import com.baidu.hugegraph.api.traverser.AllShortestPathsAPI;
+import com.baidu.hugegraph.api.traverser.CountAPI;
+import com.baidu.hugegraph.api.traverser.CrosspointsAPI;
+import com.baidu.hugegraph.api.traverser.CustomizedCrosspointsAPI;
+import com.baidu.hugegraph.api.traverser.CustomizedPathsAPI;
+import com.baidu.hugegraph.api.traverser.EdgesAPI;
+import com.baidu.hugegraph.api.traverser.FusiformSimilarityAPI;
+import com.baidu.hugegraph.api.traverser.JaccardSimilarityAPI;
+import com.baidu.hugegraph.api.traverser.KneighborAPI;
+import com.baidu.hugegraph.api.traverser.KoutAPI;
+import com.baidu.hugegraph.api.traverser.MultiNodeShortestPathAPI;
+import com.baidu.hugegraph.api.traverser.NeighborRankAPI;
+import com.baidu.hugegraph.api.traverser.PathsAPI;
+import com.baidu.hugegraph.api.traverser.PersonalRankAPI;
+import com.baidu.hugegraph.api.traverser.RaysAPI;
+import com.baidu.hugegraph.api.traverser.RingsAPI;
+import com.baidu.hugegraph.api.traverser.SameNeighborsAPI;
+import com.baidu.hugegraph.api.traverser.ShortestPathAPI;
+import com.baidu.hugegraph.api.traverser.SingleSourceShortestPathAPI;
+import com.baidu.hugegraph.api.traverser.TemplatePathsAPI;
+import com.baidu.hugegraph.api.traverser.VerticesAPI;
+import com.baidu.hugegraph.api.traverser.WeightedShortestPathAPI;
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.structure.constant.Direction;
+import com.baidu.hugegraph.structure.graph.Edge;
+import com.baidu.hugegraph.structure.graph.Edges;
+import com.baidu.hugegraph.structure.graph.GraphIterator;
+import com.baidu.hugegraph.structure.graph.Path;
+import com.baidu.hugegraph.structure.graph.Shard;
+import com.baidu.hugegraph.structure.graph.Vertex;
+import com.baidu.hugegraph.structure.graph.Vertices;
+import com.baidu.hugegraph.structure.traverser.CountRequest;
+import com.baidu.hugegraph.structure.traverser.CrosspointsRequest;
+import com.baidu.hugegraph.structure.traverser.CustomizedCrosspoints;
+import com.baidu.hugegraph.structure.traverser.MultiNodeShortestPathRequest;
+import com.baidu.hugegraph.structure.traverser.PathsWithVertices;
+import com.baidu.hugegraph.structure.traverser.FusiformSimilarity;
+import com.baidu.hugegraph.structure.traverser.FusiformSimilarityRequest;
+import com.baidu.hugegraph.structure.traverser.SingleSourceJaccardSimilarityRequest;
+import com.baidu.hugegraph.structure.traverser.Kneighbor;
+import com.baidu.hugegraph.structure.traverser.KneighborRequest;
+import com.baidu.hugegraph.structure.traverser.Kout;
+import com.baidu.hugegraph.structure.traverser.KoutRequest;
+import com.baidu.hugegraph.structure.traverser.CustomizedPathsRequest;
+import com.baidu.hugegraph.structure.traverser.PathsRequest;
+import com.baidu.hugegraph.structure.traverser.Ranks;
+import com.baidu.hugegraph.structure.traverser.TemplatePathsRequest;
+import com.baidu.hugegraph.structure.traverser.WeightedPath;
+import com.baidu.hugegraph.structure.traverser.WeightedPaths;
+import com.baidu.hugegraph.util.E;
+
+import static com.baidu.hugegraph.structure.constant.Traverser.DEFAULT_CAPACITY;
+import static com.baidu.hugegraph.structure.constant.Traverser.DEFAULT_MAX_DEGREE;
+import static com.baidu.hugegraph.structure.constant.Traverser.DEFAULT_ELEMENTS_LIMIT;
+import static com.baidu.hugegraph.structure.constant.Traverser.DEFAULT_PAGE_LIMIT;
+import static com.baidu.hugegraph.structure.constant.Traverser.DEFAULT_PATHS_LIMIT;
+
+public class TraverserManager {
+
+    private final GraphManager graphManager;
+    private JaccardSimilarityAPI jaccardSimilarityAPI;
+    private SameNeighborsAPI sameNeighborsAPI;
+    private ShortestPathAPI shortestPathAPI;
+    private AllShortestPathsAPI allShortestPathsAPI;
+    private SingleSourceShortestPathAPI singleSourceShortestPathAPI;
+    private WeightedShortestPathAPI weightedShortestPathAPI;
+    private MultiNodeShortestPathAPI multiNodeShortestPathAPI;
+    private PathsAPI pathsAPI;
+    private CrosspointsAPI crosspointsAPI;
+    private KoutAPI koutAPI;
+    private KneighborAPI kneighborAPI;
+    private CountAPI countAPI;
+    private RingsAPI ringsAPI;
+    private RaysAPI raysAPI;
+    private CustomizedPathsAPI customizedPathsAPI;
+    private CustomizedCrosspointsAPI customizedCrosspointsAPI;
+    private TemplatePathsAPI templatePathsAPI;
+    private FusiformSimilarityAPI fusiformSimilarityAPI;
+    private NeighborRankAPI neighborRankAPI;
+    private PersonalRankAPI personalRankAPI;
+    private VerticesAPI verticesAPI;
+    private EdgesAPI edgesAPI;
+
+    public TraverserManager(RestClient client, GraphManager graphManager) {
+        this.graphManager = graphManager;
+        String graph = graphManager.graph();
+        this.jaccardSimilarityAPI = new JaccardSimilarityAPI(client, graph);
+        this.sameNeighborsAPI = new SameNeighborsAPI(client, graph);
+        this.shortestPathAPI = new ShortestPathAPI(client, graph);
+        this.allShortestPathsAPI = new AllShortestPathsAPI(client, graph);
+        this.singleSourceShortestPathAPI = new SingleSourceShortestPathAPI(
+                                               client, graph);
+        this.weightedShortestPathAPI = new WeightedShortestPathAPI(
+                                           client, graph);
+        this.multiNodeShortestPathAPI = new MultiNodeShortestPathAPI(
+                                            client, graph);
+        this.pathsAPI = new PathsAPI(client, graph);
+        this.crosspointsAPI = new CrosspointsAPI(client, graph);
+        this.koutAPI = new KoutAPI(client, graph);
+        this.kneighborAPI = new KneighborAPI(client, graph);
+        this.countAPI = new CountAPI(client, graph);
+        this.ringsAPI = new RingsAPI(client, graph);
+        this.raysAPI = new RaysAPI(client, graph);
+        this.customizedPathsAPI = new CustomizedPathsAPI(client, graph);
+        this.customizedCrosspointsAPI = new CustomizedCrosspointsAPI(
+                                            client, graph);
+        this.templatePathsAPI = new TemplatePathsAPI(client, graph);
+        this.fusiformSimilarityAPI = new FusiformSimilarityAPI(client, graph);
+        this.neighborRankAPI = new NeighborRankAPI(client, graph);
+        this.personalRankAPI = new PersonalRankAPI(client, graph);
+        this.verticesAPI = new VerticesAPI(client, graph);
+        this.edgesAPI = new EdgesAPI(client, graph);
+    }
+
+    public double jaccardSimilarity(Object vertexId, Object otherId) {
+        return this.jaccardSimilarity(vertexId, otherId, DEFAULT_MAX_DEGREE);
+    }
+
+    public double jaccardSimilarity(Object vertexId, Object otherId,
+                                    long degree) {
+        return this.jaccardSimilarity(vertexId, otherId, Direction.BOTH,
+                                      null, degree);
+    }
+
+    public double jaccardSimilarity(Object vertexId, Object otherId,
+                                    Direction direction, String label,
+                                    long degree) {
+        return this.jaccardSimilarityAPI.get(vertexId, otherId, direction,
+                                             label, degree);
+    }
+
+    public Map<Object, Double> jaccardSimilarity(
+                               SingleSourceJaccardSimilarityRequest request) {
+        return this.jaccardSimilarityAPI.post(request);
+    }
+
+    public List<Object> sameNeighbors(Object vertexId, Object otherId) {
+        return this.sameNeighbors(vertexId, otherId, DEFAULT_MAX_DEGREE);
+    }
+
+    public List<Object> sameNeighbors(Object vertexId, Object otherId,
+                                      long degree) {
+        return this.sameNeighbors(vertexId, otherId, Direction.BOTH,
+                                  null, degree);
+    }
+
+    public List<Object> sameNeighbors(Object vertexId, Object otherId,
+                                      Direction direction, String label,
+                                      long degree) {
+        return this.sameNeighbors(vertexId, otherId, direction, label,
+                                  degree, DEFAULT_PATHS_LIMIT);
+    }
+
+    public List<Object> sameNeighbors(Object vertexId, Object otherId,
+                                      Direction direction, String label,
+                                      long degree, long limit) {
+        return this.sameNeighborsAPI.get(vertexId, otherId, direction,
+                                         label, degree, limit);
+    }
+
+    public Path shortestPath(Object sourceId, Object targetId, int maxDepth) {
+        return this.shortestPath(sourceId, targetId, Direction.BOTH, null,
+                                 maxDepth);
+    }
+
+    public Path shortestPath(Object sourceId, Object targetId,
+                             Direction direction, int maxDepth) {
+        return this.shortestPath(sourceId, targetId, direction, null,
+                                 maxDepth);
+    }
+
+    public Path shortestPath(Object sourceId, Object targetId,
+                             Direction direction, String label, int maxDepth) {
+        return this.shortestPath(sourceId, targetId, direction, label, maxDepth,
+                                 DEFAULT_MAX_DEGREE, DEFAULT_CAPACITY);
+    }
+
+    public Path shortestPath(Object sourceId, Object targetId,
+                             Direction direction, String label, int maxDepth,
+                             long degree, long capacity) {
+        return this.shortestPath(sourceId, targetId, direction, label,
+                                 maxDepth, degree, 0L, capacity);
+    }
+
+    public Path shortestPath(Object sourceId, Object targetId,
+                             Direction direction, String label, int maxDepth,
+                             long degree, long skipDegree, long capacity) {
+        return this.shortestPathAPI.get(sourceId, targetId, direction, label,
+                                        maxDepth, degree, skipDegree, capacity);
+    }
+
+    public List<Path> allShortestPaths(Object sourceId, Object targetId,
+                                       int maxDepth) {
+        return this.allShortestPaths(sourceId, targetId, Direction.BOTH,
+                                     null, maxDepth);
+    }
+
+    public List<Path> allShortestPaths(Object sourceId, Object targetId,
+                                       Direction direction, int maxDepth) {
+        return this.allShortestPaths(sourceId, targetId, direction,
+                                     null, maxDepth);
+    }
+
+    public List<Path> allShortestPaths(Object sourceId, Object targetId,
+                                       Direction direction, String label,
+                                       int maxDepth) {
+        return this.allShortestPaths(sourceId, targetId, direction,
+                                     label, maxDepth, DEFAULT_MAX_DEGREE,
+                                     DEFAULT_CAPACITY);
+    }
+
+    public List<Path> allShortestPaths(Object sourceId, Object targetId,
+                                       Direction direction, String label,
+                                       int maxDepth, long degree,
+                                       long capacity) {
+        return this.allShortestPaths(sourceId, targetId, direction, label,
+                                     maxDepth, degree, 0L, capacity);
+    }
+
+    public List<Path> allShortestPaths(Object sourceId, Object targetId,
+                                       Direction direction, String label,
+                                       int maxDepth, long degree,
+                                       long skipDegree, long capacity) {
+        return this.allShortestPathsAPI.get(sourceId, targetId, direction,
+                                            label, maxDepth, degree,
+                                            skipDegree, capacity);
+    }
+
+    public WeightedPaths singleSourceShortestPath(Object sourceId,
+                                                  String weight,
+                                                  boolean withVertex) {
+        return this.singleSourceShortestPath(sourceId, Direction.BOTH, null,
+                                             weight, withVertex);
+    }
+
+    public WeightedPaths singleSourceShortestPath(Object sourceId,
+                                                  Direction direction,
+                                                  String label, String weight,
+                                                  boolean withVertex) {
+        return this.singleSourceShortestPath(sourceId, direction, label, weight,
+                                             DEFAULT_MAX_DEGREE, 0L,
+                                             DEFAULT_CAPACITY,
+                                             DEFAULT_PATHS_LIMIT, withVertex);
+    }
+
+    public WeightedPaths singleSourceShortestPath(Object sourceId,
+                                                  Direction direction,
+                                                  String label, String weight,
+                                                  long degree, long skipDegree,
+                                                  long capacity, long limit,
+                                                  boolean withVertex) {
+        return this.singleSourceShortestPathAPI.get(sourceId, direction, label,
+                                                    weight, degree, skipDegree,
+                                                    capacity, limit,
+                                                    withVertex);
+    }
+
+    public WeightedPath weightedShortestPath(Object sourceId,  Object targetId,
+                                             String weight, boolean withVertex) {
+        return this.weightedShortestPath(sourceId, targetId, Direction.BOTH,
+                                         null, weight, withVertex);
+    }
+
+    public WeightedPath weightedShortestPath(Object sourceId, Object targetId,
+                                             Direction direction, String label,
+                                             String weight,
+                                             boolean withVertex) {
+        return this.weightedShortestPath(sourceId, targetId, direction, label,
+                                         weight, DEFAULT_MAX_DEGREE, 0L,
+                                         DEFAULT_CAPACITY, withVertex);
+    }
+
+    public WeightedPath weightedShortestPath(Object sourceId, Object targetId,
+                                             Direction direction,
+                                             String label, String weight,
+                                             long degree, long skipDegree,
+                                             long capacity, boolean withVertex) {
+        return this.weightedShortestPathAPI.get(sourceId, targetId,direction,
+                                                label, weight, degree,
+                                                skipDegree, capacity,
+                                                withVertex);
+    }
+
+    public PathsWithVertices multiNodeShortestPath(
+                             MultiNodeShortestPathRequest request) {
+        return this.multiNodeShortestPathAPI.post(request);
+    }
+
+    public List<Path> paths(Object sourceId, Object targetId, int maxDepth) {
+        return this.paths(sourceId, targetId, Direction.BOTH, null,
+                          maxDepth, DEFAULT_PATHS_LIMIT);
+    }
+
+    public List<Path> paths(Object sourceId, Object targetId,
+                            Direction direction, int maxDepth, long limit) {
+        return this.paths(sourceId, targetId, direction, null,
+                          maxDepth, limit);
+    }
+
+    public List<Path> paths(Object sourceId, Object targetId,
+                            Direction direction, String label,
+                            int maxDepth, long limit) {
+        return this.paths(sourceId, targetId, direction, label, maxDepth,
+                          DEFAULT_MAX_DEGREE, DEFAULT_CAPACITY, limit);
+    }
+
+    public List<Path> paths(Object sourceId, Object targetId,
+                            Direction direction, String label, int maxDepth,
+                            long degree, long capacity, long limit) {
+        return this.pathsAPI.get(sourceId, targetId, direction, label,
+                                 maxDepth, degree, capacity, limit);
+    }
+
+    public PathsWithVertices paths(PathsRequest request) {
+        return this.pathsAPI.post(request);
+    }
+
+    public List<Path> crosspoint(Object sourceId, Object targetId,
+                                 int maxDepth) {
+        return this.crosspoint(sourceId, targetId, Direction.BOTH, null,
+                               maxDepth, DEFAULT_PATHS_LIMIT);
+    }
+
+    public List<Path> crosspoint(Object sourceId, Object targetId,
+                                 Direction direction, int maxDepth,
+                                 long limit) {
+        return this.crosspoint(sourceId, targetId, direction, null,
+                               maxDepth, limit);
+    }
+
+    public List<Path> crosspoint(Object sourceId, Object targetId,
+                                 Direction direction, String label,
+                                 int maxDepth, long limit) {
+        return this.crosspoint(sourceId, targetId, direction, label, maxDepth,
+                               DEFAULT_MAX_DEGREE, DEFAULT_CAPACITY, limit);
+    }
+
+    public List<Path> crosspoint(Object sourceId, Object targetId,
+                                 Direction direction, String label,
+                                 int maxDepth, long degree, long capacity,
+                                 long limit) {
+        return this.crosspointsAPI.get(sourceId, targetId, direction, label,
+                                       maxDepth, degree, capacity, limit);
+    }
+
+    public List<Object> kout(Object sourceId, int depth) {
+        return this.kout(sourceId, Direction.BOTH, depth);
+    }
+
+    public List<Object> kout(Object sourceId, Direction direction, int depth) {
+        return this.kout(sourceId, direction, null, depth, true);
+    }
+
+    public List<Object> kout(Object sourceId, Direction direction,
+                             String label, int depth, boolean nearest) {
+        return this.kout(sourceId, direction, label, depth, nearest,
+                         DEFAULT_MAX_DEGREE, DEFAULT_CAPACITY,
+                         DEFAULT_ELEMENTS_LIMIT);
+    }
+
+    public List<Object> kout(Object sourceId, Direction direction,
+                             String label, int depth, boolean nearest,
+                             long degree, long capacity, long limit) {
+        return this.koutAPI.get(sourceId, direction, label, depth, nearest,
+                                degree, capacity, limit);
+    }
+
+    public Kout kout(KoutRequest request) {
+        return this.koutAPI.post(request);
+    }
+
+    public List<Object> kneighbor(Object sourceId, int depth) {
+        return this.kneighbor(sourceId, Direction.BOTH, null, depth);
+    }
+
+    public List<Object> kneighbor(Object sourceId, Direction direction,
+                                  int depth) {
+        return this.kneighbor(sourceId, direction, null, depth);
+    }
+
+    public List<Object> kneighbor(Object sourceId, Direction direction,
+                                  String label, int depth) {
+        return this.kneighbor(sourceId, direction, label, depth,
+                              DEFAULT_MAX_DEGREE, DEFAULT_ELEMENTS_LIMIT);
+    }
+
+    public List<Object> kneighbor(Object sourceId, Direction direction,
+                                  String label, int depth,
+                                  long degree, long limit) {
+        return this.kneighborAPI.get(sourceId, direction, label, depth,
+                                     degree, limit);
+    }
+
+    public Kneighbor kneighbor(KneighborRequest request) {
+        return this.kneighborAPI.post(request);
+    }
+
+    public long count(CountRequest request) {
+        return this.countAPI.post(request);
+    }
+
+    public List<Path> rings(Object sourceId, int depth) {
+        return this.rings(sourceId, Direction.BOTH, null, depth, true,
+                          DEFAULT_MAX_DEGREE, DEFAULT_CAPACITY,
+                          DEFAULT_ELEMENTS_LIMIT);
+    }
+
+    public List<Path> rings(Object sourceId, Direction direction, String label,
+                            int depth, boolean sourceInRing, long degree,
+                            long capacity, long limit) {
+        return this.ringsAPI.get(sourceId, direction, label, depth,
+                                 sourceInRing, degree, capacity, limit);
+    }
+
+    public List<Path> rays(Object sourceId, int depth) {
+        return this.rays(sourceId, Direction.BOTH, null, depth,
+                         DEFAULT_MAX_DEGREE, DEFAULT_CAPACITY,
+                         DEFAULT_ELEMENTS_LIMIT);
+    }
+
+    public List<Path> rays(Object sourceId, Direction direction, String label,
+                           int depth, long degree, long capacity, long limit) {
+        return this.raysAPI.get(sourceId, direction, label, depth, degree,
+                                capacity, limit);
+    }
+
+    public PathsWithVertices customizedPaths(CustomizedPathsRequest request) {
+        return this.customizedPathsAPI.post(request);
+    }
+
+    public CustomizedCrosspoints customizedCrosspointss(
+                                 CrosspointsRequest request) {
+        return this.customizedCrosspointsAPI.post(request);
+    }
+
+    public PathsWithVertices count(TemplatePathsRequest request) {
+        return this.templatePathsAPI.post(request);
+    }
+
+    public FusiformSimilarity fusiformSimilarity(
+                              FusiformSimilarityRequest request) {
+        return this.fusiformSimilarityAPI.post(request);
+    }
+
+    public List<Ranks> neighborRank(NeighborRankAPI.Request request) {
+        return this.neighborRankAPI.post(request);
+    }
+
+    public Ranks personalRank(PersonalRankAPI.Request request) {
+        return this.personalRankAPI.post(request);
+    }
+
+    public List<Shard> vertexShards(long splitSize) {
+        return this.verticesAPI.shards(splitSize);
+    }
+
+    public List<Shard> edgeShards(long splitSize) {
+        return this.edgesAPI.shards(splitSize);
+    }
+
+    public List<Vertex> vertices(List<Object> ids) {
+        List<Vertex> vertices = this.verticesAPI.list(ids);
+        for (Vertex vertex : vertices) {
+            vertex.attachManager(this.graphManager);
+        }
+        return vertices;
+    }
+
+    public Vertices vertices(Shard shard) {
+        Vertices vertices = this.vertices(shard, null, 0L);
+        E.checkState(vertices.page() == null,
+                     "Can't contains page when not in paging");
+        return vertices;
+    }
+
+    public Vertices vertices(Shard shard, String page) {
+        E.checkArgument(page != null, "Page can't be null");
+        return this.vertices(shard, page, DEFAULT_PAGE_LIMIT);
+    }
+
+    public Vertices vertices(Shard shard, String page, long pageLimit) {
+        E.checkArgument(page == null || pageLimit >= 0,
+                        "Page limit must be >= 0 when page is not null");
+        Vertices vertices = this.verticesAPI.scan(shard, page, pageLimit);
+
+        for (Vertex vertex : vertices.results()) {
+            vertex.attachManager(this.graphManager);
+        }
+        return vertices;
+    }
+
+    public Iterator<Vertex> iteratorVertices(Shard shard, int sizePerPage) {
+        return new GraphIterator<>(this.graphManager, sizePerPage, (page) -> {
+            return this.vertices(shard, page, sizePerPage);
+        });
+    }
+
+    public List<Edge> edges(List<String> ids) {
+        List<Edge> edges = this.edgesAPI.list(ids);
+        for (Edge edge : edges) {
+            edge.attachManager(this.graphManager);
+        }
+        return edges;
+    }
+
+    public Edges edges(Shard shard) {
+        Edges edges = this.edges(shard, null, 0L);
+        E.checkState(edges.page() == null,
+                     "Can't contains page when not in paging");
+        return edges;
+    }
+
+    public Edges edges(Shard shard, String page) {
+        E.checkArgument(page != null, "Page can't be null");
+        return this.edges(shard, page, DEFAULT_PAGE_LIMIT);
+    }
+
+    public Edges edges(Shard shard, String page, long pageLimit) {
+        E.checkArgument(page == null || pageLimit >= 0,
+                        "Page limit must be >= 0 when page is not null");
+        Edges edges = this.edgesAPI.scan(shard, page, pageLimit);
+        for (Edge edge : edges.results()) {
+            edge.attachManager(this.graphManager);
+        }
+        return edges;
+    }
+
+    public Iterator<Edge> iteratorEdges(Shard shard, int sizePerPage) {
+        return new GraphIterator<>(this.graphManager, sizePerPage, (page) -> {
+            return this.edges(shard, page, sizePerPage);
+        });
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/driver/VariablesManager.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/driver/VariablesManager.java
new file mode 100644
index 0000000..ee6e1f6
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/driver/VariablesManager.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.driver;
+
+import com.baidu.hugegraph.api.variables.VariablesAPI;
+import com.baidu.hugegraph.client.RestClient;
+
+import java.util.Map;
+
+public class VariablesManager {
+
+    private VariablesAPI variablesAPI;
+
+    public VariablesManager(RestClient client, String graph) {
+        this.variablesAPI = new VariablesAPI(client, graph);
+    }
+
+    public Map<String, Object> get(String key) {
+        return this.variablesAPI.get(key);
+    }
+
+    public Map<String, Object> set(String key, Object value) {
+        return this.variablesAPI.set(key, value);
+    }
+
+    public void remove(String key) {
+        this.variablesAPI.remove(key);
+    }
+
+    public Map<String, Object> all() {
+        return this.variablesAPI.all();
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/driver/VersionManager.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/driver/VersionManager.java
new file mode 100644
index 0000000..2ff9956
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/driver/VersionManager.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.driver;
+
+import com.baidu.hugegraph.api.version.VersionAPI;
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.structure.version.Versions;
+
+public class VersionManager {
+
+    private VersionAPI versionAPI;
+
+    public VersionManager(RestClient client) {
+        this.versionAPI = new VersionAPI(client);
+    }
+
+    public String getCoreVersion() {
+        Versions versions = this.versionAPI.get();
+        return versions.get("core");
+    }
+
+    public String getGremlinVersion() {
+        Versions versions = this.versionAPI.get();
+        return versions.get("gremlin");
+    }
+
+    public String getApiVersion() {
+        Versions versions = this.versionAPI.get();
+        return versions.get("api");
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/example/BatchExample.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/example/BatchExample.java
new file mode 100644
index 0000000..94f59e5
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/example/BatchExample.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.example;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.baidu.hugegraph.driver.GraphManager;
+import com.baidu.hugegraph.driver.HugeClient;
+import com.baidu.hugegraph.driver.SchemaManager;
+import com.baidu.hugegraph.structure.graph.Edge;
+import com.baidu.hugegraph.structure.graph.Vertex;
+
+public class BatchExample {
+
+    public static void main(String[] args) {
+        // If connect failed will throw a exception.
+        HugeClient hugeClient = HugeClient.builder("http://localhost:8080",
+                                                   "hugegraph").build();
+
+        SchemaManager schema = hugeClient.schema();
+
+        schema.propertyKey("name").asText().ifNotExist().create();
+        schema.propertyKey("age").asInt().ifNotExist().create();
+        schema.propertyKey("lang").asText().ifNotExist().create();
+        schema.propertyKey("date").asDate().ifNotExist().create();
+        schema.propertyKey("price").asInt().ifNotExist().create();
+
+        schema.vertexLabel("person")
+              .properties("name", "age")
+              .primaryKeys("name")
+              .ifNotExist()
+              .create();
+
+        schema.vertexLabel("person")
+              .properties("price")
+              .nullableKeys("price")
+              .append();
+
+        schema.vertexLabel("software")
+              .properties("name", "lang", "price")
+              .primaryKeys("name")
+              .ifNotExist()
+              .create();
+
+        schema.indexLabel("softwareByPrice")
+              .onV("software").by("price")
+              .range()
+              .ifNotExist()
+              .create();
+
+        schema.edgeLabel("knows")
+              .link("person", "person")
+              .properties("date")
+              .ifNotExist()
+              .create();
+
+        schema.edgeLabel("created")
+              .link("person", "software")
+              .properties("date")
+              .ifNotExist()
+              .create();
+
+        schema.indexLabel("createdByDate")
+              .onE("created").by("date")
+              .secondary()
+              .ifNotExist()
+              .create();
+
+        // get schema object by name
+        System.out.println(schema.getPropertyKey("name"));
+        System.out.println(schema.getVertexLabel("person"));
+        System.out.println(schema.getEdgeLabel("knows"));
+        System.out.println(schema.getIndexLabel("createdByDate"));
+
+        // list all schema objects
+        System.out.println(schema.getPropertyKeys());
+        System.out.println(schema.getVertexLabels());
+        System.out.println(schema.getEdgeLabels());
+        System.out.println(schema.getIndexLabels());
+
+        GraphManager graph = hugeClient.graph();
+
+        Vertex marko = new Vertex("person").property("name", "marko")
+                                           .property("age", 29);
+        Vertex vadas = new Vertex("person").property("name", "vadas")
+                                           .property("age", 27);
+        Vertex lop = new Vertex("software").property("name", "lop")
+                                           .property("lang", "java")
+                                           .property("price", 328);
+        Vertex josh = new Vertex("person").property("name", "josh")
+                                          .property("age", 32);
+        Vertex ripple = new Vertex("software").property("name", "ripple")
+                                              .property("lang", "java")
+                                              .property("price", 199);
+        Vertex peter = new Vertex("person").property("name", "peter")
+                                           .property("age", 35);
+
+        Edge markoKnowsVadas = new Edge("knows").source(marko).target(vadas)
+                                                .property("date", "2016-01-10");
+        Edge markoKnowsJosh = new Edge("knows").source(marko).target(josh)
+                                               .property("date", "2013-02-20");
+        Edge markoCreateLop = new Edge("created").source(marko).target(lop)
+                                                 .property("date",
+                                                           "2017-12-10");
+        Edge joshCreateRipple = new Edge("created").source(josh).target(ripple)
+                                                   .property("date",
+                                                             "2017-12-10");
+        Edge joshCreateLop = new Edge("created").source(josh).target(lop)
+                                                .property("date", "2009-11-11");
+        Edge peterCreateLop = new Edge("created").source(peter).target(lop)
+                                                 .property("date",
+                                                           "2017-03-24");
+
+        List<Vertex> vertices = new ArrayList<>();
+        vertices.add(marko);
+        vertices.add(vadas);
+        vertices.add(lop);
+        vertices.add(josh);
+        vertices.add(ripple);
+        vertices.add(peter);
+
+        List<Edge> edges = new ArrayList<>();
+        edges.add(markoKnowsVadas);
+        edges.add(markoKnowsJosh);
+        edges.add(markoCreateLop);
+        edges.add(joshCreateRipple);
+        edges.add(joshCreateLop);
+        edges.add(peterCreateLop);
+
+        vertices = graph.addVertices(vertices);
+        vertices.forEach(vertex -> System.out.println(vertex));
+
+        edges = graph.addEdges(edges, false);
+        edges.forEach(edge -> System.out.println(edge));
+
+        hugeClient.close();
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/example/MovieExample.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/example/MovieExample.java
new file mode 100644
index 0000000..88bf5ca
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/example/MovieExample.java
@@ -0,0 +1,582 @@
+package com.baidu.hugegraph.example;
+
+import com.baidu.hugegraph.driver.GraphManager;
+import com.baidu.hugegraph.driver.HugeClient;
+import com.baidu.hugegraph.driver.SchemaManager;
+import com.baidu.hugegraph.structure.constant.T;
+import com.baidu.hugegraph.structure.graph.Vertex;
+
+public class MovieExample {
+
+    public static void main(String[] args) {
+        // If connect failed will throw a exception.
+        HugeClient hugeClient = HugeClient.builder("http://localhost:8080",
+                                                   "hugegraph").build();
+
+        SchemaManager schema = hugeClient.schema();
+
+        schema.propertyKey("name").asText().ifNotExist().create();
+        schema.propertyKey("born").asInt().ifNotExist().create();
+        schema.propertyKey("title").asText().ifNotExist().create();
+        schema.propertyKey("released").asInt().ifNotExist().create();
+        schema.propertyKey("score").asInt().ifNotExist().create();
+        schema.propertyKey("roles").asText().ifNotExist().create();
+
+        schema.vertexLabel("person")
+              .properties("name", "born")
+              .primaryKeys("name")
+              .ifNotExist()
+              .create();
+        schema.vertexLabel("movie")
+              .properties("title", "released")
+              .primaryKeys("title")
+              .ifNotExist()
+              .create();
+
+        schema.edgeLabel("ACTED_IN").multiTimes().properties("roles")
+              .sourceLabel("person").targetLabel("movie")
+              .sortKeys("roles")
+              .ifNotExist()
+              .create();
+        schema.edgeLabel("DIRECTED").properties("score")
+              .sourceLabel("person").targetLabel("movie")
+              .ifNotExist()
+              .create();
+        schema.edgeLabel("PRODUCED").properties("score")
+              .sourceLabel("person").targetLabel("movie")
+              .ifNotExist()
+              .create();
+        schema.edgeLabel("WROTE").properties("score")
+              .sourceLabel("person").targetLabel("movie")
+              .ifNotExist()
+              .create();
+
+        schema.indexLabel("personByBorn").onV("person").by("born")
+              .range().ifNotExist().create();
+
+        GraphManager graph = hugeClient.graph();
+
+        Vertex theMatrix = graph.addVertex(T.label, "movie", "title", "The Matrix", "released", 1999);
+        Vertex keanu = graph.addVertex(T.label, "person", "name", "keanu Reeves", "born", 1964);
+        Vertex carrie = graph.addVertex(T.label, "person", "name", "carrie-anne Moss", "born", 1967);
+        Vertex laurence = graph.addVertex(T.label, "person", "name", "laurence Fishburne", "born", 1961);
+        Vertex hugo = graph.addVertex(T.label, "person", "name", "hugo Weaving", "born", 1960);
+        Vertex lillyW = graph.addVertex(T.label, "person", "name", "Lilly Wachowski", "born", 1967);
+        Vertex lanaW = graph.addVertex(T.label, "person", "name", "Lana Wachowski", "born", 1965);
+        Vertex joelS = graph.addVertex(T.label, "person", "name", "Joel Silver", "born", 1952);
+
+        keanu.addEdge("ACTED_IN", theMatrix, "roles", "Neo");
+        carrie.addEdge("ACTED_IN", theMatrix, "roles", "Trinity");
+        laurence.addEdge("ACTED_IN", theMatrix, "roles", "Morpheus");
+        hugo.addEdge("ACTED_IN", theMatrix, "roles", "agent Smith");
+        lillyW.addEdge("DIRECTED", theMatrix, "score", 10);
+        lanaW.addEdge("DIRECTED", theMatrix, "score", 10);
+        joelS.addEdge("PRODUCED", theMatrix, "score", 10);
+
+        Vertex emil = graph.addVertex(T.label, "person", "name", "emil Eifrem", "born", 1978);
+        emil.addEdge("ACTED_IN", theMatrix, "roles", "emil");
+
+        Vertex theMatrixReloaded = graph.addVertex(T.label, "movie", "title", "The Matrix Reloaded", "released", 2003);
+
+        keanu.addEdge("ACTED_IN", theMatrixReloaded, "roles", "Neo");
+        carrie.addEdge("ACTED_IN", theMatrixReloaded, "roles", "Trinity");
+        laurence.addEdge("ACTED_IN", theMatrixReloaded, "roles", "Morpheus");
+        hugo.addEdge("ACTED_IN", theMatrixReloaded, "roles", "agent Smith");
+        lillyW.addEdge("DIRECTED", theMatrixReloaded, "score", 10);
+        lanaW.addEdge("DIRECTED", theMatrix, "score", 10);
+        joelS.addEdge("PRODUCED", theMatrixReloaded, "score", 10);
+
+        Vertex theMatrixRevolutions = graph.addVertex(T.label, "movie", "title", "The Matrix Revolutions", "released", 2003);
+
+        keanu.addEdge("ACTED_IN", theMatrixRevolutions, "roles", "Neo");
+        carrie.addEdge("ACTED_IN", theMatrixRevolutions, "roles", "Trinity");
+        laurence.addEdge("ACTED_IN", theMatrixRevolutions, "roles", "Morpheus");
+        hugo.addEdge("ACTED_IN", theMatrixRevolutions, "roles", "agent Smith");
+        lillyW.addEdge("DIRECTED", theMatrixRevolutions, "score", 10);
+        lanaW.addEdge("DIRECTED", theMatrixRevolutions, "score", 10);
+        joelS.addEdge("PRODUCED", theMatrixRevolutions, "score", 10);
+
+        Vertex theDevilsadvocate = graph.addVertex(T.label, "movie", "title", "The Devil's advocate", "released", 1997);
+
+        Vertex charlize = graph.addVertex(T.label, "person", "name", "charlize Theron", "born", 1975);
+        Vertex al = graph.addVertex(T.label, "person", "name", "al Pacino", "born", 1940);
+        Vertex taylor = graph.addVertex(T.label, "person", "name", "taylor Hackford", "born", 1944);
+
+        keanu.addEdge("ACTED_IN", theDevilsadvocate, "roles", "Kevin Lomax");
+        charlize.addEdge("ACTED_IN", theDevilsadvocate, "roles", "Mary ann Lomax");
+        al.addEdge("ACTED_IN", theDevilsadvocate, "roles", "John Milton");
+        taylor.addEdge("DIRECTED", theDevilsadvocate, "score", 10);
+
+        Vertex aFewGoodMen = graph.addVertex(T.label, "movie", "title", "a Few Good Men", "released", 1992);
+
+        Vertex tomC = graph.addVertex(T.label, "person", "name", "Tom Cruise", "born", 1962);
+        Vertex jackN = graph.addVertex(T.label, "person", "name", "Jack Nicholson", "born", 1937);
+        Vertex demiM = graph.addVertex(T.label, "person", "name", "Demi Moore", "born", 1962);
+        Vertex kevinB = graph.addVertex(T.label, "person", "name", "Kevin Bacon", "born", 1958);
+        Vertex kieferS = graph.addVertex(T.label, "person", "name", "Kiefer Sutherland", "born", 1966);
+        Vertex noahW = graph.addVertex(T.label, "person", "name", "Noah Wyle", "born", 1971);
+        Vertex cubaG = graph.addVertex(T.label, "person", "name", "Cuba Gooding Jr.", "born", 1968);
+        Vertex kevinP = graph.addVertex(T.label, "person", "name", "Kevin Pollak", "born", 1957);
+        Vertex jtw = graph.addVertex(T.label, "person", "name", "J.T. Walsh", "born", 1943);
+        Vertex jamesM = graph.addVertex(T.label, "person", "name", "James Marshall", "born", 1967);
+        Vertex christopherG = graph.addVertex(T.label, "person", "name", "Christopher Guest", "born", 1948);
+        Vertex robR = graph.addVertex(T.label, "person", "name", "Rob Reiner", "born", 1947);
+        Vertex aaronS = graph.addVertex(T.label, "person", "name", "aaron Sorkin", "born", 1961);
+
+        tomC.addEdge("ACTED_IN", aFewGoodMen, "roles", "Lt. Daniel Kaffee");
+        jackN.addEdge("ACTED_IN", aFewGoodMen, "roles", "Col. nathan R. Jessup");
+        demiM.addEdge("ACTED_IN", aFewGoodMen, "roles", "Lt. Cdr. Joanne Galloway");
+        kevinB.addEdge("ACTED_IN", aFewGoodMen, "roles", "Capt. Jack Ross");
+        kieferS.addEdge("ACTED_IN", aFewGoodMen, "roles", "Lt. Jonathan Kendrick");
+        noahW.addEdge("ACTED_IN", aFewGoodMen, "roles", "Cpl. Jeffrey Barnes");
+        cubaG.addEdge("ACTED_IN", aFewGoodMen, "roles", "Cpl. Carl Hammaker");
+        kevinP.addEdge("ACTED_IN", aFewGoodMen, "roles", "Lt. Sam Weinberg");
+        jtw.addEdge("ACTED_IN", aFewGoodMen, "roles", "Lt. Col. Matthew andrew Markinson");
+        jamesM.addEdge("ACTED_IN", aFewGoodMen, "roles", "Pfc. Louden Downey");
+        christopherG.addEdge("ACTED_IN", aFewGoodMen, "roles", "Dr. Stone");
+        aaronS.addEdge("ACTED_IN", aFewGoodMen, "roles", "Man in Bar");
+        robR.addEdge("DIRECTED", aFewGoodMen, "score", 10);
+        aaronS.addEdge("WROTE", aFewGoodMen, "score", 10);
+
+        Vertex topGun = graph.addVertex(T.label, "movie", "title", "Top Gun", "released", 1986);
+
+        Vertex kellyM = graph.addVertex(T.label, "person", "name", "Kelly McGillis", "born", 1957);
+        Vertex valK = graph.addVertex(T.label, "person", "name", "Val Kilmer", "born", 1959);
+        Vertex anthonyE = graph.addVertex(T.label, "person", "name", "anthony Edwards", "born", 1962);
+        Vertex tomS = graph.addVertex(T.label, "person", "name", "Tom Skerritt", "born", 1933);
+        Vertex megR = graph.addVertex(T.label, "person", "name", "Meg Ryan", "born", 1961);
+        Vertex tonyS = graph.addVertex(T.label, "person", "name", "Tony Scott", "born", 1944);
+        Vertex jimC = graph.addVertex(T.label, "person", "name", "Jim Cash", "born", 1941);
+
+        tomC.addEdge("ACTED_IN", topGun, "roles", "Maverick");
+        kellyM.addEdge("ACTED_IN", topGun, "roles", "Charlie");
+        valK.addEdge("ACTED_IN", topGun, "roles", "Iceman");
+        anthonyE.addEdge("ACTED_IN", topGun, "roles", "Goose");
+        tomS.addEdge("ACTED_IN", topGun, "roles", "Viper");
+        megR.addEdge("ACTED_IN", topGun, "roles", "Carole");
+        tonyS.addEdge("DIRECTED", topGun, "score", 10);
+        jimC.addEdge("WROTE", topGun, "score", 10);
+
+        Vertex jerryMaguire = graph.addVertex(T.label, "movie", "title", "Jerry Maguire", "released", 2000);
+
+        Vertex reneeZ = graph.addVertex(T.label, "person", "name", "Renee Zellweger", "born", 1969);
+        Vertex kellyP = graph.addVertex(T.label, "person", "name", "Kelly Preston", "born", 1962);
+        Vertex jerryO = graph.addVertex(T.label, "person", "name", "Jerry O'Connell", "born", 1974);
+        Vertex jayM = graph.addVertex(T.label, "person", "name", "Jay Mohr", "born", 1970);
+        Vertex bonnieH = graph.addVertex(T.label, "person", "name", "Bonnie Hunt", "born", 1970);
+        Vertex reginaK = graph.addVertex(T.label, "person", "name", "Regina King", "born", 1961);
+        Vertex jonathanL = graph.addVertex(T.label, "person", "name", "Jonathan Lipnicki", "born", 1996);
+        Vertex cameronC = graph.addVertex(T.label, "person", "name", "Cameron Crowe", "born", 1957);
+
+        tomC.addEdge("ACTED_IN", jerryMaguire, "roles", "Jerry Maguire");
+        cubaG.addEdge("ACTED_IN", jerryMaguire, "roles", "Rod Tidwell");
+        reneeZ.addEdge("ACTED_IN", jerryMaguire, "roles", "Dorothy Boyd");
+        kellyP.addEdge("ACTED_IN", jerryMaguire, "roles", "avery Bishop");
+        jerryO.addEdge("ACTED_IN", jerryMaguire, "roles", "Frank Cushman");
+        jayM.addEdge("ACTED_IN", jerryMaguire, "roles", "Bob Sugar");
+        bonnieH.addEdge("ACTED_IN", jerryMaguire, "roles", "Laurel Boyd");
+        reginaK.addEdge("ACTED_IN", jerryMaguire, "roles", "Marcee Tidwell");
+        jonathanL.addEdge("ACTED_IN", jerryMaguire, "roles", "Ray Boyd");
+        cameronC.addEdge("DIRECTED", jerryMaguire, "score", 10);
+        cameronC.addEdge("PRODUCED", jerryMaguire, "score", 10);
+        cameronC.addEdge("WROTE", jerryMaguire, "score", 10);
+
+        Vertex standByMe = graph.addVertex(T.label, "movie", "title", "Stand By Me", "released", 1986);
+
+        Vertex riverP = graph.addVertex(T.label, "person", "name", "River Phoenix", "born", 1970);
+        Vertex coreyF = graph.addVertex(T.label, "person", "name", "Corey Feldman", "born", 1971);
+        Vertex wilW = graph.addVertex(T.label, "person", "name", "Wil Wheaton", "born", 1972);
+        Vertex johnC = graph.addVertex(T.label, "person", "name", "John Cusack", "born", 1966);
+        Vertex marshallB = graph.addVertex(T.label, "person", "name", "Marshall Bell", "born", 1942);
+
+        wilW.addEdge("ACTED_IN", standByMe, "roles", "Gordie Lachance");
+        riverP.addEdge("ACTED_IN", standByMe, "roles", "Chris Chambers");
+        jerryO.addEdge("ACTED_IN", standByMe, "roles", "Vern Tessio");
+        coreyF.addEdge("ACTED_IN", standByMe, "roles", "Teddy Duchamp");
+        johnC.addEdge("ACTED_IN", standByMe, "roles", "Denny Lachance");
+        kieferS.addEdge("ACTED_IN", standByMe, "roles", "ace Merrill");
+        marshallB.addEdge("ACTED_IN", standByMe, "roles", "Mr. Lachance");
+        robR.addEdge("DIRECTED", standByMe, "score", 10);
+
+        Vertex asGoodasItGets = graph.addVertex(T.label, "movie", "title", "as Good as It Gets", "released", 1997);
+
+        Vertex helenH = graph.addVertex(T.label, "person", "name", "Helen Hunt", "born", 1963);
+        Vertex gregK = graph.addVertex(T.label, "person", "name", "Greg Kinnear", "born", 1963);
+        Vertex jamesB = graph.addVertex(T.label, "person", "name", "James L. Brooks", "born", 1940);
+
+        jackN.addEdge("ACTED_IN", asGoodasItGets, "roles", "Melvin Udall");
+        helenH.addEdge("ACTED_IN", asGoodasItGets, "roles", "Carol Connelly");
+        gregK.addEdge("ACTED_IN", asGoodasItGets, "roles", "Simon Bishop");
+        cubaG.addEdge("ACTED_IN", asGoodasItGets, "roles", "Frank Sachs");
+        jamesB.addEdge("DIRECTED", asGoodasItGets, "score", 10);
+
+        Vertex whatDreamsMayCome = graph.addVertex(T.label, "movie", "title", "What Dreams May Come", "released", 1998);
+
+        Vertex annabellaS = graph.addVertex(T.label, "person", "name", "annabella Sciorra", "born", 1960);
+        Vertex maxS = graph.addVertex(T.label, "person", "name", "Max von Sydow", "born", 1929);
+        Vertex wernerH = graph.addVertex(T.label, "person", "name", "Werner Herzog", "born", 1942);
+        Vertex robin = graph.addVertex(T.label, "person", "name", "robin Williams", "born", 1951);
+        Vertex vincentW = graph.addVertex(T.label, "person", "name", "Vincent Ward", "born", 1956);
+
+        robin.addEdge("ACTED_IN", whatDreamsMayCome, "roles", "Chris Nielsen");
+        cubaG.addEdge("ACTED_IN", whatDreamsMayCome, "roles", "albert Lewis");
+        annabellaS.addEdge("ACTED_IN", whatDreamsMayCome, "roles", "annie Collins-Nielsen");
+        maxS.addEdge("ACTED_IN", whatDreamsMayCome, "roles", "The Tracker");
+        wernerH.addEdge("ACTED_IN", whatDreamsMayCome, "roles", "The Face");
+        vincentW.addEdge("DIRECTED", whatDreamsMayCome, "score", 10);
+
+        Vertex snowFallingonCedars = graph.addVertex(T.label, "movie", "title", "Snow Falling on Cedars", "released", 1999);
+
+        Vertex ethanH = graph.addVertex(T.label, "person", "name", "Ethan Hawke", "born", 1970);
+        Vertex rickY = graph.addVertex(T.label, "person", "name", "Rick Yune", "born", 1971);
+        Vertex jamesC = graph.addVertex(T.label, "person", "name", "James Cromwell", "born", 1940);
+        Vertex scottH = graph.addVertex(T.label, "person", "name", "Scott Hicks", "born", 1953);
+
+        ethanH.addEdge("ACTED_IN", snowFallingonCedars, "roles", "Ishmael Chambers");
+        rickY.addEdge("ACTED_IN", snowFallingonCedars, "roles", "Kazuo Miyamoto");
+        maxS.addEdge("ACTED_IN", snowFallingonCedars, "roles", "Nels Gudmundsson");
+        jamesC.addEdge("ACTED_IN", snowFallingonCedars, "roles", "Judge Fielding");
+        scottH.addEdge("DIRECTED", snowFallingonCedars, "score", 10);
+
+        Vertex youveGotMail = graph.addVertex(T.label, "movie", "title", "You've Got Mail", "released", 1998);
+
+        Vertex parkerP = graph.addVertex(T.label, "person", "name", "Parker Posey", "born", 1968);
+        Vertex daveC = graph.addVertex(T.label, "person", "name", "Dave Chappelle", "born", 1973);
+        Vertex steveZ = graph.addVertex(T.label, "person", "name", "Steve Zahn", "born", 1967);
+        Vertex tomH = graph.addVertex(T.label, "person", "name", "Tom Hanks", "born", 1956);
+        Vertex noraE = graph.addVertex(T.label, "person", "name", "Nora Ephron", "born", 1941);
+
+        tomH.addEdge("ACTED_IN", youveGotMail, "roles", "Joe Fox");
+        megR.addEdge("ACTED_IN", youveGotMail, "roles", "Kathleen Kelly");
+        gregK.addEdge("ACTED_IN", youveGotMail, "roles", "Frank Navasky");
+        parkerP.addEdge("ACTED_IN", youveGotMail, "roles", "Patricia Eden");
+        daveC.addEdge("ACTED_IN", youveGotMail, "roles", "Kevin Jackson");
+        steveZ.addEdge("ACTED_IN", youveGotMail, "roles", "George Pappas");
+        noraE.addEdge("DIRECTED", youveGotMail, "score", 10);
+
+        Vertex sleeplessInSeattle = graph.addVertex(T.label, "movie", "title", "Sleepless in Seattle", "released", 1993);
+
+        Vertex ritaW = graph.addVertex(T.label, "person", "name", "Rita Wilson", "born", 1956);
+        Vertex billPull = graph.addVertex(T.label, "person", "name", "Bill Pullman", "born", 1953);
+        Vertex victorG = graph.addVertex(T.label, "person", "name", "Victor Garber", "born", 1949);
+        Vertex rosieO = graph.addVertex(T.label, "person", "name", "Rosie O'Donnell", "born", 1962);
+
+        tomH.addEdge("ACTED_IN", sleeplessInSeattle, "roles", "Sam Baldwin");
+        megR.addEdge("ACTED_IN", sleeplessInSeattle, "roles", "annie Reed");
+        ritaW.addEdge("ACTED_IN", sleeplessInSeattle, "roles", "Suzy");
+        billPull.addEdge("ACTED_IN", sleeplessInSeattle, "roles", "Walter");
+        victorG.addEdge("ACTED_IN", sleeplessInSeattle, "roles", "Greg");
+        rosieO.addEdge("ACTED_IN", sleeplessInSeattle, "roles", "Becky");
+        noraE.addEdge("DIRECTED", sleeplessInSeattle, "score", 10);
+
+        Vertex joeVersustheVolcano = graph.addVertex(T.label, "movie", "title", "Joe Versus the Volcano", "released", 1990);
+
+        Vertex johnS = graph.addVertex(T.label, "person", "name", "John Patrick Stanley", "born", 1950);
+        Vertex nathan = graph.addVertex(T.label, "person", "name", "nathan Lane", "born", 1956);
+
+        tomH.addEdge("ACTED_IN", joeVersustheVolcano, "roles", "Joe Banks");
+        megR.addEdge("ACTED_IN", joeVersustheVolcano, "roles", "DeDe, angelica Graynamore, Patricia Graynamore");
+        nathan.addEdge("ACTED_IN", joeVersustheVolcano, "roles", "Baw");
+        johnS.addEdge("DIRECTED", joeVersustheVolcano, "score", 10);
+
+        Vertex whenHarryMetSally = graph.addVertex(T.label, "movie", "title", "When Harry Met Sally", "released", 1998);
+
+        Vertex billyC = graph.addVertex(T.label, "person", "name", "Billy Crystal", "born", 1948);
+        Vertex carrieF = graph.addVertex(T.label, "person", "name", "carrie Fisher", "born", 1956);
+        Vertex brunoK = graph.addVertex(T.label, "person", "name", "Bruno Kirby", "born", 1949);
+
+        billyC.addEdge("ACTED_IN", whenHarryMetSally, "roles", "Harry Burns");
+        megR.addEdge("ACTED_IN", whenHarryMetSally, "roles", "Sally albright");
+        carrieF.addEdge("ACTED_IN", whenHarryMetSally, "roles", "Marie");
+        brunoK.addEdge("ACTED_IN", whenHarryMetSally, "roles", "Jess");
+        robR.addEdge("DIRECTED", whenHarryMetSally, "score", 10);
+        robR.addEdge("PRODUCED", whenHarryMetSally, "score", 10);
+        noraE.addEdge("PRODUCED", whenHarryMetSally, "score", 10);
+        noraE.addEdge("WROTE", whenHarryMetSally, "score", 10);
+
+        Vertex thatThingYouDo = graph.addVertex(T.label, "movie", "title", "That Thing You Do", "released", 1996);
+
+        Vertex livT = graph.addVertex(T.label, "person", "name", "Liv Tyler", "born", 1977);
+
+        tomH.addEdge("ACTED_IN", thatThingYouDo, "roles", "Mr. White");
+        livT.addEdge("ACTED_IN", thatThingYouDo, "roles", "Faye Dolan");
+        charlize.addEdge("ACTED_IN", thatThingYouDo, "roles", "Tina");
+        tomH.addEdge("DIRECTED", thatThingYouDo, "score", 10);
+
+        Vertex theReplacements = graph.addVertex(T.label, "movie", "title", "The Replacements", "released", 2000);
+
+        Vertex brooke = graph.addVertex(T.label, "person", "name", "brooke Langton", "born", 1970);
+        Vertex gene = graph.addVertex(T.label, "person", "name", "gene Hackman", "born", 1930);
+        Vertex orlando = graph.addVertex(T.label, "person", "name", "orlando Jones", "born", 1968);
+        Vertex howard = graph.addVertex(T.label, "person", "name", "howard Deutch", "born", 1950);
+
+        keanu.addEdge("ACTED_IN", theReplacements, "roles", "Shane Falco");
+        brooke.addEdge("ACTED_IN", theReplacements, "roles", "annabelle Farrell");
+        gene.addEdge("ACTED_IN", theReplacements, "roles", "Jimmy McGinty");
+        orlando.addEdge("ACTED_IN", theReplacements, "roles", "Clifford Franklin");
+        howard.addEdge("DIRECTED", theReplacements, "score", 10);
+
+        Vertex rescueDawn = graph.addVertex(T.label, "movie", "title", "rescueDawn", "released", 2006);
+
+        Vertex christianB = graph.addVertex(T.label, "person", "name", "Christian Bale", "born", 1974);
+        Vertex zachG = graph.addVertex(T.label, "person", "name", "Zach Grenier", "born", 1954);
+
+        marshallB.addEdge("ACTED_IN", rescueDawn, "roles", "admiral");
+        christianB.addEdge("ACTED_IN", rescueDawn, "roles", "Dieter Dengler");
+        zachG.addEdge("ACTED_IN", rescueDawn, "roles", "Squad Leader");
+        steveZ.addEdge("ACTED_IN", rescueDawn, "roles", "Duane");
+        wernerH.addEdge("DIRECTED", rescueDawn, "score", 10);
+
+        Vertex theBirdcage = graph.addVertex(T.label, "movie", "title", "The Birdcage", "released", 1996);
+
+        Vertex mikeN = graph.addVertex(T.label, "person", "name", "Mike Nichols", "born", 1931);
+
+        robin.addEdge("ACTED_IN", theBirdcage, "roles", "armand Goldman");
+        nathan.addEdge("ACTED_IN", theBirdcage, "roles", "albert Goldman");
+        gene.addEdge("ACTED_IN", theBirdcage, "roles", "Sen. Kevin Keeley");
+        mikeN.addEdge("DIRECTED", theBirdcage, "score", 10);
+
+        Vertex unforgiven = graph.addVertex(T.label, "movie", "title", "unforgiven", "released", 1992);
+
+        Vertex richardH = graph.addVertex(T.label, "person", "name", "Richard Harris", "born", 1930);
+        Vertex clintE = graph.addVertex(T.label, "person", "name", "Richard Harris", "born", 1930);
+
+        richardH.addEdge("ACTED_IN", unforgiven, "roles", "English Bob");
+        clintE.addEdge("ACTED_IN", unforgiven, "roles", "Bill Munny");
+        gene.addEdge("ACTED_IN", unforgiven, "roles", "Little Bill Daggett");
+        clintE.addEdge("DIRECTED", unforgiven, "score", 10);
+
+        Vertex johnnyMnemonic = graph.addVertex(T.label, "movie", "title", "Johnny Mnemonic", "released", 1995);
+
+        Vertex takeshi = graph.addVertex(T.label, "person", "name", "takeshi Kitano", "born", 1947);
+        Vertex dina = graph.addVertex(T.label, "person", "name", "dina Meyer", "born", 1968);
+        Vertex iceT = graph.addVertex(T.label, "person", "name", "Ice-T", "born", 1958);
+        Vertex robertL = graph.addVertex(T.label, "person", "name", "Robert Longo", "born", 1953);
+
+        keanu.addEdge("ACTED_IN", johnnyMnemonic, "roles", "Johnny Mnemonic");
+        takeshi.addEdge("ACTED_IN", johnnyMnemonic, "roles", "Takahashi");
+        dina.addEdge("ACTED_IN", johnnyMnemonic, "roles", "Jane");
+        iceT.addEdge("ACTED_IN", johnnyMnemonic, "roles", "J-Bone");
+        robertL.addEdge("DIRECTED", johnnyMnemonic, "score", 10);
+
+        Vertex cloudatlas = graph.addVertex(T.label, "movie", "title", "Cloud atlas", "released", 2012);
+
+        Vertex halleB = graph.addVertex(T.label, "person", "name", "Halle Berry", "born", 1966);
+        Vertex jimB = graph.addVertex(T.label, "person", "name", "Jim Broadbent", "born", 1949);
+        Vertex tomT = graph.addVertex(T.label, "person", "name", "Tom Tykwer", "born", 1965);
+        Vertex davidMitchell = graph.addVertex(T.label, "person", "name", "David Mitchell", "born", 1969);
+        Vertex stefanarndt = graph.addVertex(T.label, "person", "name", "Stefan arndt", "born", 1961);
+
+        tomH.addEdge("ACTED_IN", cloudatlas, "roles", "Zachry, Dr. Henry Goose, Isaac Sachs, Dermot Hoggins");
+        hugo.addEdge("ACTED_IN", cloudatlas, "roles", "Bill Smoke, Haskell Moore, Tadeusz Kesselring, Nurse Noakes,"
+                + " Boardman Mephi, Old Georgie");
+        halleB.addEdge("ACTED_IN", cloudatlas, "roles", "Luisa Rey, Jocasta ayrs, Ovid, Meronym");
+        jimB.addEdge("ACTED_IN", cloudatlas, "roles", "Vyvyan ayrs, Captain Molyneux, Timothy Cavendish");
+        tomT.addEdge("DIRECTED", cloudatlas, "score", 10);
+        lillyW.addEdge("DIRECTED", cloudatlas, "score", 10);
+        lanaW.addEdge("DIRECTED", cloudatlas, "score", 10);
+        davidMitchell.addEdge("WROTE", cloudatlas, "score", 10);
+        stefanarndt.addEdge("PRODUCED", cloudatlas, "score", 10);
+
+        Vertex theDaVinciCode = graph.addVertex(T.label, "movie", "title", "The Da Vinci Code", "released", 2006);
+
+        Vertex ianM = graph.addVertex(T.label, "person", "name", "Ian McKellen", "born", 1939);
+        Vertex audreyT = graph.addVertex(T.label, "person", "name", "audrey Tautou", "born", 1976);
+        Vertex paulB = graph.addVertex(T.label, "person", "name", "Paul Bettany", "born", 1971);
+        Vertex ronH = graph.addVertex(T.label, "person", "name", "Ron howard", "born", 1954);
+
+        tomH.addEdge("ACTED_IN", theDaVinciCode, "roles", "Dr. Robert Langdon");
+        ianM.addEdge("ACTED_IN", theDaVinciCode, "roles", "Sir Leight Teabing");
+        audreyT.addEdge("ACTED_IN", theDaVinciCode, "roles", "Sophie Neveu");
+        paulB.addEdge("ACTED_IN", theDaVinciCode, "roles", "Silas");
+        ronH.addEdge("DIRECTED", theDaVinciCode, "score", 10);
+
+        Vertex vforVendetta = graph.addVertex(T.label, "movie", "title", "The Da Vinci Code", "released", 2006);
+
+        Vertex natalieP = graph.addVertex(T.label, "person", "name", "Natalie Portman", "born", 1981);
+        Vertex stephenR = graph.addVertex(T.label, "person", "name", "Stephen Rea", "born", 1946);
+        Vertex johnH = graph.addVertex(T.label, "person", "name", "John Hurt", "born", 1940);
+        Vertex benM = graph.addVertex(T.label, "person", "name", "Ben Miles", "born", 1967);
+
+        hugo.addEdge("ACTED_IN", vforVendetta, "roles", "V");
+        natalieP.addEdge("ACTED_IN", vforVendetta, "roles", "Evey Hammond");
+        stephenR.addEdge("ACTED_IN", vforVendetta, "roles", "Eric Finch");
+        johnH.addEdge("ACTED_IN", vforVendetta, "roles", "High Chancellor adam Sutler");
+        benM.addEdge("ACTED_IN", vforVendetta, "roles", "Dascomb");
+        jamesM.addEdge("DIRECTED", vforVendetta, "score", 10);
+        lillyW.addEdge("PRODUCED", vforVendetta, "score", 10);
+        lanaW.addEdge("PRODUCED", vforVendetta, "score", 10);
+        joelS.addEdge("PRODUCED", vforVendetta, "score", 10);
+        lillyW.addEdge("WROTE", vforVendetta, "score", 10);
+        lanaW.addEdge("WROTE", vforVendetta, "score", 10);
+
+        Vertex speedRacer = graph.addVertex(T.label, "movie", "title", "Speed Racer", "released", 2008);
+
+        Vertex matthewF = graph.addVertex(T.label, "person", "name", "Matthew Fox", "born", 1966);
+        Vertex emileH = graph.addVertex(T.label, "person", "name", "Emile Hirsch", "born", 1985);
+        Vertex johnG = graph.addVertex(T.label, "person", "name", "John Goodman", "born", 1940);
+        Vertex susanS = graph.addVertex(T.label, "person", "name", "Susan Sarandon", "born", 1966);
+        Vertex christinaR = graph.addVertex(T.label, "person", "name", "Christina Ricci", "born", 1980);
+        Vertex rain = graph.addVertex(T.label, "person", "name", "Rain", "born", 1982);
+
+        emileH.addEdge("ACTED_IN", speedRacer, "roles", "Speed Racer");
+        johnG.addEdge("ACTED_IN", speedRacer, "roles", "Pops");
+        susanS.addEdge("ACTED_IN", speedRacer, "roles", "Mom");
+        matthewF.addEdge("ACTED_IN", speedRacer, "roles", "Racer X");
+        christinaR.addEdge("ACTED_IN", speedRacer, "roles", "Trixie");
+        rain.addEdge("ACTED_IN", speedRacer, "roles", "Taejo Togokahn");
+        benM.addEdge("ACTED_IN", speedRacer, "roles", "Kass Jones");
+        lillyW.addEdge("DIRECTED", speedRacer, "score", 10);
+        lanaW.addEdge("DIRECTED", speedRacer, "score", 10);
+        lillyW.addEdge("WROTE", speedRacer, "score", 10);
+        lanaW.addEdge("WROTE", speedRacer, "score", 10);
+        joelS.addEdge("PRODUCED", speedRacer, "score", 10);
+
+        Vertex ninjaassassin = graph.addVertex(T.label, "movie", "title", "Speed Racer", "released", 2009);
+
+        Vertex naomieH = graph.addVertex(T.label, "person", "name", "Naomie Harris", "born", 1982);
+
+        rain.addEdge("ACTED_IN", ninjaassassin, "roles", "Raizo");
+        naomieH.addEdge("ACTED_IN", ninjaassassin, "roles", "Mika Coretti");
+        rickY.addEdge("ACTED_IN", ninjaassassin, "roles", "takeshi");
+        benM.addEdge("ACTED_IN", ninjaassassin, "roles", "Ryan Maslow");
+        jamesM.addEdge("DIRECTED", ninjaassassin, "score", 10);
+        lillyW.addEdge("PRODUCED", ninjaassassin, "score", 10);
+        lanaW.addEdge("PRODUCED", ninjaassassin, "score", 10);
+        joelS.addEdge("PRODUCED", ninjaassassin, "score", 10);
+
+        Vertex theGreenMile = graph.addVertex(T.label, "movie", "title", "The Green Mile", "released", 1999);
+
+        Vertex michaelD = graph.addVertex(T.label, "person", "name", "Michael Clarke Duncan", "born", 1957);
+        Vertex davidM = graph.addVertex(T.label, "person", "name", "David Morse", "born", 1953);
+        Vertex samR = graph.addVertex(T.label, "person", "name", "Sam Rockwell", "born", 1968);
+        Vertex garyS = graph.addVertex(T.label, "person", "name", "Gary Sinise", "born", 1955);
+        Vertex patriciaC = graph.addVertex(T.label, "person", "name", "Patricia Clarkson", "born", 1959);
+        Vertex frankD = graph.addVertex(T.label, "person", "name", "Frank Darabont", "born", 1959);
+
+        tomH.addEdge("ACTED_IN", theGreenMile, "roles", "Paul Edgecomb");
+        michaelD.addEdge("ACTED_IN", theGreenMile, "roles", "John Coffey");
+        davidM.addEdge("ACTED_IN", theGreenMile, "roles", "Brutus");
+        bonnieH.addEdge("ACTED_IN", theGreenMile, "roles", "Jan Edgecomb");
+        jamesC.addEdge("ACTED_IN", theGreenMile, "roles", "Warden Hal Moores");
+        samR.addEdge("ACTED_IN", theGreenMile, "roles", "Wild Bill' Wharton");
+        garyS.addEdge("ACTED_IN", theGreenMile, "roles", "Burt Hammersmith");
+        patriciaC.addEdge("ACTED_IN", theGreenMile, "roles", "Melinda Moores");
+        frankD.addEdge("DIRECTED", theGreenMile, "score", 10);
+
+        Vertex frostNixon = graph.addVertex(T.label, "movie", "title", "Frost/Nixon", "released", 2008);
+
+        Vertex frankL = graph.addVertex(T.label, "person", "name", "Frank Langella", "born", 1938);
+        Vertex michaelS = graph.addVertex(T.label, "person", "name", "Michael Sheen", "born", 1969);
+        Vertex oliverP = graph.addVertex(T.label, "person", "name", "Oliver Platt", "born", 1960);
+
+        frankL.addEdge("ACTED_IN", frostNixon, "roles", "Richard Nixon");
+        michaelS.addEdge("ACTED_IN", frostNixon, "roles", "David Frost");
+        kevinB.addEdge("ACTED_IN", frostNixon, "roles", "Jack Brennan");
+        oliverP.addEdge("ACTED_IN", frostNixon, "roles", "Bob Zelnick");
+        samR.addEdge("ACTED_IN", frostNixon, "roles", "James Reston, Jr.");
+        ronH.addEdge("DIRECTED", frostNixon, "score", 10);
+
+        Vertex hoffa = graph.addVertex(T.label, "movie", "title", "hoffa", "released", 1992);
+
+        Vertex dannyD = graph.addVertex(T.label, "person", "name", "Danny DeVito", "born", 1944);
+        Vertex johnR = graph.addVertex(T.label, "person", "name", "John C. Reilly", "born", 1965);
+
+        jackN.addEdge("ACTED_IN", hoffa, "roles", "hoffa");
+        dannyD.addEdge("ACTED_IN", hoffa, "roles", "Robert Ciaro");
+        jtw.addEdge("ACTED_IN", hoffa, "roles", "Frank Fitzsimmons");
+        johnR.addEdge("ACTED_IN", hoffa, "roles", "Peter Connelly");
+        dannyD.addEdge("DIRECTED", hoffa, "score", 10);
+
+        Vertex apollo13 = graph.addVertex(T.label, "movie", "title", "apollo 13", "released", 1995);
+
+        Vertex edH = graph.addVertex(T.label, "person", "name", "Ed Harris", "born", 1950);
+        Vertex billPax = graph.addVertex(T.label, "person", "name", "Bill Paxton", "born", 1955);
+
+        tomH.addEdge("ACTED_IN", apollo13, "roles", "Jim Lovell");
+        kevinB.addEdge("ACTED_IN", apollo13, "roles", "Jack Swigert");
+        edH.addEdge("ACTED_IN", apollo13, "roles", "gene Kranz");
+        billPax.addEdge("ACTED_IN", apollo13, "roles", "Fred Haise");
+        garyS.addEdge("ACTED_IN", apollo13, "roles", "Ken Mattingly");
+        ronH.addEdge("DIRECTED", apollo13, "score", 10);
+
+        Vertex twister = graph.addVertex(T.label, "movie", "title", "twister", "released", 1996);
+
+        Vertex philipH = graph.addVertex(T.label, "person", "name", "Philip Seymour Hoffman", "born", 1967);
+        Vertex janB = graph.addVertex(T.label, "person", "name", "Jan de Bont", "born", 1943);
+
+        billPax.addEdge("ACTED_IN", twister, "roles", "Bill Harding");
+        helenH.addEdge("ACTED_IN", twister, "roles", "Dr. Jo Harding");
+        zachG.addEdge("ACTED_IN", twister, "roles", "Eddie");
+        philipH.addEdge("ACTED_IN", twister, "roles", "Dustin 'Dusty' Davis");
+        janB.addEdge("DIRECTED", twister, "score", 10);
+
+        Vertex castaway = graph.addVertex(T.label, "movie", "title", "Cast away", "released", 2000);
+
+        Vertex robertZ = graph.addVertex(T.label, "person", "name", "Robert Zemeckis", "born", 1951);
+
+        tomH.addEdge("ACTED_IN", castaway, "roles", "Chuck Noland");
+        helenH.addEdge("ACTED_IN", castaway, "roles", "Kelly Frears");
+        robertZ.addEdge("DIRECTED", castaway, "score", 10);
+
+        Vertex oneFlewOvertheCuckoosNest = graph.addVertex(T.label, "movie", "title", "One Flew Over the Cuckoo's Nest", "released", 1975);
+
+        Vertex milosF = graph.addVertex(T.label, "person", "name", "Milos Forman", "born", 1932);
+
+        jackN.addEdge("ACTED_IN", oneFlewOvertheCuckoosNest, "roles", "Randle McMurphy");
+        dannyD.addEdge("ACTED_IN", oneFlewOvertheCuckoosNest, "roles", "Martini");
+        milosF.addEdge("DIRECTED", oneFlewOvertheCuckoosNest, "score", 10);
+
+        Vertex somethingsGottaGive = graph.addVertex(T.label, "movie", "title", "Something's Gotta Give", "released", 2003);
+
+        Vertex dianeK = graph.addVertex(T.label, "person", "name", "Diane Keaton", "born", 1946);
+        Vertex nancyM = graph.addVertex(T.label, "person", "name", "Nancy Meyers", "born", 1949);
+
+        jackN.addEdge("ACTED_IN", somethingsGottaGive, "roles", "Harry Sanborn");
+        dianeK.addEdge("ACTED_IN", somethingsGottaGive, "roles", "Erica Barry");
+        keanu.addEdge("ACTED_IN", somethingsGottaGive, "roles", "Julian Mercer");
+        nancyM.addEdge("DIRECTED", somethingsGottaGive, "score", 10);
+        nancyM.addEdge("PRODUCED", somethingsGottaGive, "score", 10);
+        nancyM.addEdge("WROTE", somethingsGottaGive, "score", 10);
+
+        Vertex bicentennialMan = graph.addVertex(T.label, "movie", "title", "Bicentennial Man", "released", 2000);
+
+        Vertex chrisC = graph.addVertex(T.label, "person", "name", "Chris Columbus", "born", 1958);
+
+        robin.addEdge("ACTED_IN", bicentennialMan, "roles", "andrew Marin");
+        oliverP.addEdge("ACTED_IN", bicentennialMan, "roles", "Rupert Burns");
+        chrisC.addEdge("DIRECTED", bicentennialMan, "score", 10);
+
+        Vertex charlieWilsonsWar = graph.addVertex(T.label, "movie", "title", "Charlie Wilson's War", "released", 2007);
+
+        Vertex juliaR = graph.addVertex(T.label, "person", "name", "Julia Roberts", "born", 1967);
+
+        tomH.addEdge("ACTED_IN", charlieWilsonsWar, "roles", "Rep. Charlie Wilson");
+        juliaR.addEdge("ACTED_IN", charlieWilsonsWar, "roles", "Joanne Herring");
+        philipH.addEdge("ACTED_IN", charlieWilsonsWar, "roles", "Gust avrakotos");
+        mikeN.addEdge("DIRECTED", charlieWilsonsWar, "score", 10);
+
+        Vertex thePolarExpress = graph.addVertex(T.label, "movie", "title", "The Polar Express", "released", 2004);
+
+        tomH.addEdge("ACTED_IN", thePolarExpress, "roles", "Hero Boy");
+
+        robertZ.addEdge("DIRECTED", thePolarExpress, "score", 10);
+
+        Vertex aLeagueofTheirOwn = graph.addVertex(T.label, "movie", "title", "a League of Their Own", "released", 1992);
+
+        Vertex madonna = graph.addVertex(T.label, "person", "name", "madonna", "born", 1954);
+        Vertex geenaD = graph.addVertex(T.label, "person", "name", "Geena Davis", "born", 1956);
+        Vertex loriP = graph.addVertex(T.label, "person", "name", "Lori Petty", "born", 1963);
+        Vertex pennyM = graph.addVertex(T.label, "person", "name", "Penny Marshall", "born", 1943);
+
+        tomH.addEdge("ACTED_IN", aLeagueofTheirOwn, "roles", "Jimmy Dugan");
+        geenaD.addEdge("ACTED_IN", aLeagueofTheirOwn, "roles", "Dottie Hinson");
+        loriP.addEdge("ACTED_IN", aLeagueofTheirOwn, "roles", "Kit Keller");
+        rosieO.addEdge("ACTED_IN", aLeagueofTheirOwn, "roles", "Doris Murphy");
+        madonna.addEdge("ACTED_IN", aLeagueofTheirOwn, "roles", "all the Way' Mae Mordabito");
+        billPax.addEdge("ACTED_IN", aLeagueofTheirOwn, "roles", "Bob Hinson");
+        pennyM.addEdge("DIRECTED", aLeagueofTheirOwn, "score", 10);
+
+        hugeClient.close();
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/example/SingleExample.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/example/SingleExample.java
new file mode 100644
index 0000000..d7e0523
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/example/SingleExample.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.example;
+
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.List;
+
+import com.baidu.hugegraph.driver.GraphManager;
+import com.baidu.hugegraph.driver.GremlinManager;
+import com.baidu.hugegraph.driver.HugeClient;
+import com.baidu.hugegraph.driver.SchemaManager;
+import com.baidu.hugegraph.structure.constant.T;
+import com.baidu.hugegraph.structure.graph.Edge;
+import com.baidu.hugegraph.structure.graph.Path;
+import com.baidu.hugegraph.structure.graph.Vertex;
+import com.baidu.hugegraph.structure.gremlin.Result;
+import com.baidu.hugegraph.structure.gremlin.ResultSet;
+
+public class SingleExample {
+
+    public static void main(String[] args) throws IOException {
+        // If connect failed will throw a exception.
+        HugeClient hugeClient = HugeClient.builder("http://localhost:8080",
+                                                   "hugegraph").build();
+
+        SchemaManager schema = hugeClient.schema();
+
+        schema.propertyKey("name").asText().ifNotExist().create();
+        schema.propertyKey("age").asInt().ifNotExist().create();
+        schema.propertyKey("city").asText().ifNotExist().create();
+        schema.propertyKey("weight").asDouble().ifNotExist().create();
+        schema.propertyKey("lang").asText().ifNotExist().create();
+        schema.propertyKey("date").asDate().ifNotExist().create();
+        schema.propertyKey("price").asInt().ifNotExist().create();
+
+        schema.vertexLabel("person")
+              .properties("name", "age", "city")
+              .primaryKeys("name")
+              .ifNotExist()
+              .create();
+
+        schema.vertexLabel("software")
+              .properties("name", "lang", "price")
+              .primaryKeys("name")
+              .ifNotExist()
+              .create();
+
+        schema.indexLabel("personByCity")
+              .onV("person")
+              .by("city")
+              .secondary()
+              .ifNotExist()
+              .create();
+
+        schema.indexLabel("personByAgeAndCity")
+              .onV("person")
+              .by("age", "city")
+              .secondary()
+              .ifNotExist()
+              .create();
+
+        schema.indexLabel("softwareByPrice")
+              .onV("software")
+              .by("price")
+              .range()
+              .ifNotExist()
+              .create();
+
+        schema.edgeLabel("knows")
+              .sourceLabel("person")
+              .targetLabel("person")
+              .properties("date", "weight")
+              .ifNotExist()
+              .create();
+
+        schema.edgeLabel("created")
+              .sourceLabel("person").targetLabel("software")
+              .properties("date", "weight")
+              .ifNotExist()
+              .create();
+
+        schema.indexLabel("createdByDate")
+              .onE("created")
+              .by("date")
+              .secondary()
+              .ifNotExist()
+              .create();
+
+        schema.indexLabel("createdByWeight")
+              .onE("created")
+              .by("weight")
+              .range()
+              .ifNotExist()
+              .create();
+
+        schema.indexLabel("knowsByWeight")
+              .onE("knows")
+              .by("weight")
+              .range()
+              .ifNotExist()
+              .create();
+
+        GraphManager graph = hugeClient.graph();
+        Vertex marko = graph.addVertex(T.label, "person", "name", "marko",
+                                       "age", 29, "city", "Beijing");
+        Vertex vadas = graph.addVertex(T.label, "person", "name", "vadas",
+                                       "age", 27, "city", "Hongkong");
+        Vertex lop = graph.addVertex(T.label, "software", "name", "lop",
+                                     "lang", "java", "price", 328);
+        Vertex josh = graph.addVertex(T.label, "person", "name", "josh",
+                                      "age", 32, "city", "Beijing");
+        Vertex ripple = graph.addVertex(T.label, "software", "name", "ripple",
+                                        "lang", "java", "price", 199);
+        Vertex peter = graph.addVertex(T.label, "person", "name", "peter",
+                                       "age", 35, "city", "Shanghai");
+
+        marko.addEdge("knows", vadas, "date", "2016-01-10", "weight", 0.5);
+        marko.addEdge("knows", josh, "date", "2013-02-20", "weight", 1.0);
+        marko.addEdge("created", lop, "date", "2017-12-10", "weight", 0.4);
+        josh.addEdge("created", lop, "date", "2009-11-11", "weight", 0.4);
+        josh.addEdge("created", ripple, "date", "2017-12-10", "weight", 1.0);
+        peter.addEdge("created", lop, "date", "2017-03-24", "weight", 0.2);
+
+        GremlinManager gremlin = hugeClient.gremlin();
+        System.out.println("==== Path ====");
+        ResultSet resultSet = gremlin.gremlin("g.V().outE().path()").execute();
+        Iterator<Result> results = resultSet.iterator();
+        results.forEachRemaining(result -> {
+            System.out.println(result.getObject().getClass());
+            Object object = result.getObject();
+            if (object instanceof Vertex) {
+                System.out.println(((Vertex) object).id());
+            } else if (object instanceof Edge) {
+                System.out.println(((Edge) object).id());
+            } else if (object instanceof Path) {
+                List<Object> elements = ((Path) object).objects();
+                elements.forEach(element -> {
+                    System.out.println(element.getClass());
+                    System.out.println(element);
+                });
+            } else {
+                System.out.println(object);
+            }
+        });
+
+        hugeClient.close();
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/exception/InvalidOperationException.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/exception/InvalidOperationException.java
new file mode 100644
index 0000000..4a09898
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/exception/InvalidOperationException.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.exception;
+
+import com.baidu.hugegraph.rest.ClientException;
+
+public class InvalidOperationException extends ClientException {
+
+    private static final long serialVersionUID = -7618213317796656644L;
+
+    public InvalidOperationException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public InvalidOperationException(String message, Object... args) {
+        super(message, args);
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/exception/InvalidResponseException.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/exception/InvalidResponseException.java
new file mode 100644
index 0000000..7c95042
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/exception/InvalidResponseException.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.exception;
+
+import com.baidu.hugegraph.rest.ClientException;
+
+public class InvalidResponseException extends ClientException {
+
+    private static final long serialVersionUID = -6837901607110262081L;
+
+    public InvalidResponseException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public InvalidResponseException(String message, Object... args) {
+        super(message, args);
+    }
+
+    public static InvalidResponseException expectField(String expectField,
+                                                       Object parentField) {
+        return new InvalidResponseException(
+                   "Invalid response, expect '%s' in '%s'",
+                   expectField, parentField);
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/exception/NotAllCreatedException.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/exception/NotAllCreatedException.java
new file mode 100644
index 0000000..80e59a8
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/exception/NotAllCreatedException.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.exception;
+
+import java.util.Collection;
+
+public class NotAllCreatedException extends ServerException {
+
+    private static final long serialVersionUID = -8795820552805040556L;
+
+    private Collection<?> ids;
+
+    public NotAllCreatedException(String message, Collection<?> ids,
+                                  Object... args) {
+        super(message, args);
+        this.ids = ids;
+    }
+
+    public Collection<?> ids() {
+        return this.ids;
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/exception/NotSupportException.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/exception/NotSupportException.java
new file mode 100644
index 0000000..f91ba0f
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/exception/NotSupportException.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.exception;
+
+import com.baidu.hugegraph.rest.ClientException;
+
+public class NotSupportException extends ClientException {
+
+    private static final long serialVersionUID = -8711375282196157056L;
+
+    private static final String PREFIX = "Not support ";
+
+    public NotSupportException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public NotSupportException(String message, Object... args) {
+        super(PREFIX + message, args);
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/exception/ServerException.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/exception/ServerException.java
new file mode 100644
index 0000000..ea77229
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/exception/ServerException.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.exception;
+
+import java.util.Map;
+
+import com.baidu.hugegraph.rest.RestResult;
+import jakarta.ws.rs.core.Response;
+
+public class ServerException extends RuntimeException {
+
+    private static final long serialVersionUID = 6335623004322652358L;
+
+    private static final String[] EXCEPTION_KEYS = {"exception",
+                                                    "Exception-Class"};
+    private static final String[] MESSAGE_KEYS = {"message"};
+    private static final String[] CAUSE_KEYS = {"cause", "exceptions"};
+    private static final String[] TRACE_KEYS = {"trace", "stackTrace"};
+
+
+    private int status = 0;
+    private String exception;
+    private String message;
+    private String cause;
+    private Object trace;
+
+    public static ServerException fromResponse(Response response) {
+        RestResult rs = new RestResult(response);
+        ServerException exception = new ServerException(rs.content());
+        exception.status(response.getStatus());
+        try {
+            @SuppressWarnings("unchecked")
+            Map<String, Object> json = rs.readObject(Map.class);
+            exception.exception = (String) getByKeys(json, EXCEPTION_KEYS);
+            exception.message =  (String) getByKeys(json, MESSAGE_KEYS);
+            exception.cause =  (String) getByKeys(json, CAUSE_KEYS);
+            exception.trace = getByKeys(json, TRACE_KEYS);
+        } catch (Exception ignored) {}
+
+        return exception;
+    }
+
+    public ServerException(String message) {
+        this.message = message;
+    }
+
+    public ServerException(String message, Object... args) {
+        this(String.format(message, args));
+    }
+
+    public String exception() {
+        return this.exception;
+    }
+
+    public String message() {
+        return this.message;
+    }
+
+    public String cause() {
+        return this.cause;
+    }
+
+    public Object trace() {
+        return this.trace;
+    }
+
+    @Override
+    public String getMessage() {
+        return this.message;
+    }
+
+    @Override
+    public Throwable getCause() {
+        if (this.cause() == null || this.cause().isEmpty()) {
+            return null;
+        }
+        return new ServerCause(this.cause());
+    }
+
+    public void status(int status) {
+        this.status = status;
+    }
+
+    public int status() {
+        return this.status;
+    }
+
+    @Override
+    public String toString() {
+        String s = this.exception;
+        String message = getLocalizedMessage();
+        return (message != null) ? (s + ": " + message) : s;
+    }
+
+    private static Object getByKeys(Map<String, Object> json, String[] keys) {
+        for (String key : keys) {
+            if (json.containsKey(key)) {
+                return json.get(key);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * The stack trace of server exception
+     */
+    private static class ServerCause extends RuntimeException {
+
+        private static final long serialVersionUID = 8755660573085501031L;
+
+        public ServerCause(String cause) {
+            super(cause, null, true, false);
+        }
+
+        @Override
+        public String toString() {
+            return super.getMessage();
+        }
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/serializer/PathDeserializer.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/serializer/PathDeserializer.java
new file mode 100644
index 0000000..264aab1
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/serializer/PathDeserializer.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.serializer;
+
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.List;
+
+import com.baidu.hugegraph.exception.InvalidResponseException;
+import com.baidu.hugegraph.structure.graph.Edge;
+import com.baidu.hugegraph.structure.graph.Path;
+import com.baidu.hugegraph.structure.graph.Vertex;
+import com.baidu.hugegraph.util.JsonUtil;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.JsonNodeType;
+
+public class PathDeserializer extends JsonDeserializer<Path> {
+
+    @Override
+    public Path deserialize(JsonParser parser, DeserializationContext ctxt)
+                            throws IOException {
+
+        JsonNode node = parser.getCodec().readTree(parser);
+        Path path = new Path();
+
+        // Parse node 'labels'
+        JsonNode labelsNode = node.get("labels");
+        if (labelsNode != null) {
+            if (labelsNode.getNodeType() != JsonNodeType.ARRAY) {
+                throw InvalidResponseException.expectField("labels", node);
+            }
+            Object labels = JsonUtil.convertValue(labelsNode, Object.class);
+            ((List<?>) labels).forEach(path::labels);
+        }
+
+        // Parse node 'objects'
+        JsonNode objectsNode = node.get("objects");
+        if (objectsNode == null ||
+            objectsNode.getNodeType() != JsonNodeType.ARRAY) {
+            throw InvalidResponseException.expectField("objects", node);
+        }
+
+        Iterator<JsonNode> objects = objectsNode.elements();
+        while (objects.hasNext()) {
+            JsonNode objectNode = objects.next();
+            JsonNode typeNode = objectNode.get("type");
+            Object object;
+            if (typeNode != null) {
+                object = parseTypedNode(objectNode, typeNode);
+            } else {
+                object = JsonUtil.convertValue(objectNode, Object.class);
+            }
+            path.objects(object);
+        }
+
+        // Parse node 'crosspoint'
+        JsonNode crosspointNode = node.get("crosspoint");
+        if (crosspointNode != null) {
+            Object object = JsonUtil.convertValue(crosspointNode, Object.class);
+            path.crosspoint(object);
+        }
+        return path;
+    }
+
+    private Object parseTypedNode(JsonNode objectNode, JsonNode typeNode) {
+        String type = typeNode.asText();
+        if ("vertex".equals(type)) {
+            return JsonUtil.convertValue(objectNode, Vertex.class);
+        } else if ("edge".equals(type)) {
+            return JsonUtil.convertValue(objectNode, Edge.class);
+        } else {
+            throw InvalidResponseException.expectField("vertex/edge", type);
+        }
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/Element.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/Element.java
new file mode 100644
index 0000000..4ad8a6c
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/Element.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure;
+
+import java.util.Objects;
+
+public abstract class Element {
+
+    public abstract String type();
+
+    public abstract Object id();
+
+    @Override
+    public int hashCode() {
+        return this.id().hashCode();
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        }
+        if (other == null || this.getClass() != other.getClass()) {
+            return false;
+        }
+        return Objects.equals(this.id(), ((Element) other).id());
+    }
+
+    @Override
+    public String toString() {
+        return String.format("%s(type %s)", this.id(), this.type());
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/GraphElement.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/GraphElement.java
new file mode 100644
index 0000000..042882c
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/GraphElement.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+
+import com.baidu.hugegraph.driver.GraphManager;
+import com.baidu.hugegraph.structure.constant.GraphAttachable;
+import com.baidu.hugegraph.util.E;
+import com.baidu.hugegraph.util.ReflectionUtil;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public abstract class GraphElement extends Element implements GraphAttachable {
+
+    // Hold a graphManager object to call graphApi
+    protected GraphManager manager;
+
+    @JsonProperty("label")
+    protected String label;
+    @JsonProperty("type")
+    protected String type;
+    @JsonProperty("properties")
+    protected Map<String, Object> properties;
+
+    public GraphElement() {
+        this.properties = new ConcurrentHashMap<>();
+    }
+
+    @Override
+    public void attachManager(GraphManager manager) {
+        this.manager = manager;
+    }
+
+    public String label() {
+        return this.label;
+    }
+
+    @Override
+    public String type() {
+        return this.type;
+    }
+
+    protected boolean fresh() {
+        return this.manager == null;
+    }
+
+    public Object property(String key) {
+        return this.properties.get(key);
+    }
+
+    public GraphElement property(String name, Object value) {
+        E.checkArgumentNotNull(name, "property name");
+        E.checkArgumentNotNull(value, "property value");
+
+        Class<?> clazz = value.getClass();
+        E.checkArgument(ReflectionUtil.isSimpleType(clazz) ||
+                        clazz.equals(UUID.class) ||
+                        clazz.equals(Date.class) ||
+                        value instanceof List ||
+                        value instanceof Set,
+                        "Invalid property value type: '%s'", clazz);
+
+        this.properties.put(name, value);
+        return this;
+    }
+
+    public Map<String, Object> properties() {
+        return this.properties;
+    }
+
+    protected abstract GraphElement setProperty(String key, Object value);
+
+    public abstract GraphElement removeProperty(String key);
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/SchemaElement.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/SchemaElement.java
new file mode 100644
index 0000000..fe1131b
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/SchemaElement.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentSkipListSet;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public abstract class SchemaElement extends Element {
+
+    @JsonProperty("id")
+    protected long id;
+    @JsonProperty("name")
+    protected String name;
+    @JsonProperty("properties")
+    protected Set<String> properties;
+    @JsonProperty("check_exist")
+    protected boolean checkExist;
+    @JsonProperty("user_data")
+    protected Map<String, Object> userdata;
+    @JsonProperty("status")
+    protected String status;
+
+    public SchemaElement(String name) {
+        this.name = name;
+        this.properties = new ConcurrentSkipListSet<>();
+        this.userdata = new ConcurrentHashMap<>();
+        this.checkExist = true;
+        this.status = null;
+    }
+
+    @Override
+    public Long id() {
+        return this.id;
+    }
+
+    public void resetId() {
+        this.id = 0L;
+    }
+
+    public String name() {
+        return this.name;
+    }
+
+    public Set<String> properties() {
+        return this.properties;
+    }
+
+    public Map<String, Object> userdata() {
+        return this.userdata;
+    }
+
+    public String status() {
+        return this.status;
+    }
+
+    public boolean checkExist() {
+        return this.checkExist;
+    }
+
+    public void checkExist(boolean checkExist) {
+        this.checkExist = checkExist;
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/Task.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/Task.java
new file mode 100644
index 0000000..bffc1a1
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/Task.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import com.baidu.hugegraph.util.E;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.collect.ImmutableSet;
+
+public class Task {
+
+    public static final long TASK_ID_NULL = 0L;
+
+    @JsonProperty
+    private long id;
+
+    @JsonProperty(P.TYPE)
+    private String type;
+
+    @JsonProperty(P.NAME)
+    private String name;
+
+    @JsonProperty(P.STATUS)
+    private String status;
+
+    @JsonProperty(P.CALLABLE)
+    private String callable;
+
+    @JsonProperty(P.CREATE)
+    private long create;
+
+    @JsonProperty(P.UPDATE)
+    private long update;
+
+    @JsonProperty(P.PROGRESS)
+    private long progress;
+
+    @JsonProperty(P.RETRIES)
+    private long retries;
+
+    @JsonProperty(P.INPUT)
+    private String input;
+
+    @JsonProperty(P.RESULT)
+    private Object result;
+
+    @JsonProperty(P.DESCRIPTION)
+    private String description;
+
+    @JsonProperty(P.DEPENDENCIES)
+    private Set<Long> dependencies;
+
+    @JsonProperty(P.SERVER)
+    private String server;
+
+    public long id() {
+        return this.id;
+    }
+
+    public String type() {
+        return this.type;
+    }
+
+    public String name() {
+        return this.name;
+    }
+
+    public String status() {
+        return this.status;
+    }
+
+    public String callable() {
+        return this.callable;
+    }
+
+    public long createTime() {
+        return this.create;
+    }
+
+    public long updateTime() {
+        return this.update;
+    }
+
+    public long progress() {
+        return this.progress;
+    }
+
+    public long retries() {
+        return this.retries;
+    }
+
+    public String input() {
+        return this.input;
+    }
+
+    public Object result() {
+        return this.result;
+    }
+
+    public String description() {
+        return this.description;
+    }
+
+    public Set<Long> dependencies() {
+        return this.dependencies;
+    }
+
+    public String server() {
+        return this.server;
+    }
+
+    public boolean completed() {
+        return ImmutableSet.of("success", "failed", "cancelled")
+                           .contains(this.status);
+    }
+
+    public boolean cancelled() {
+        return "cancelled".equals(this.status);
+    }
+
+    public boolean cancelling() {
+        return "cancelling".equals(this.status);
+    }
+
+    public boolean success() {
+        return "success".equals(this.status);
+    }
+
+    public Map<String, Object> asMap() {
+        E.checkState(this.name != null, "Task name can't be null");
+
+        Map<String, Object> map = new HashMap<>();
+
+        map.put(P.ID, this.id);
+        map.put(P.TYPE, this.type);
+        map.put(P.NAME, this.name);
+        map.put(P.CALLABLE, this.callable);
+        map.put(P.STATUS, this.status);
+        map.put(P.PROGRESS, this.progress);
+        map.put(P.CREATE, this.create);
+        map.put(P.RETRIES, this.retries);
+        if (this.description != null) {
+            map.put(P.DESCRIPTION, this.description);
+        }
+        if (this.update != 0) {
+            map.put(P.UPDATE, this.update);
+        }
+        if (this.input != null) {
+            map.put(P.INPUT, this.input);
+        }
+        if (this.result != null) {
+            map.put(P.RESULT, this.result);
+        }
+        if (this.dependencies != null) {
+            map.put(P.DEPENDENCIES, this.dependencies);
+        }
+        if (this.server != null) {
+            map.put(P.SERVER, this.server);
+        }
+
+        return map;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("Task(id=%s)", this.id);
+    }
+
+    public static final class P {
+
+        public static final String ID = "id";
+        public static final String TYPE = "task_type";
+        public static final String NAME = "task_name";
+        public static final String CALLABLE = "task_callable";
+        public static final String DESCRIPTION = "task_description";
+        public static final String STATUS = "task_status";
+        public static final String PROGRESS = "task_progress";
+        public static final String CREATE = "task_create";
+        public static final String UPDATE = "task_update";
+        public static final String RETRIES = "task_retries";
+        public static final String INPUT = "task_input";
+        public static final String RESULT = "task_result";
+        public static final String DEPENDENCIES = "task_dependencies";
+        public static final String SERVER = "task_server";
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/auth/Access.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/auth/Access.java
new file mode 100644
index 0000000..770ccb4
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/auth/Access.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.auth;
+
+import java.util.Date;
+
+import com.baidu.hugegraph.structure.constant.HugeType;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class Access extends AuthElement {
+
+    @JsonProperty("group")
+    private Object group;
+    @JsonProperty("target")
+    private Object target;
+    @JsonProperty("access_permission")
+    private HugePermission permission;
+    @JsonProperty("access_description")
+    private String description;
+
+    @JsonProperty("access_create")
+    @JsonFormat(pattern = DATE_FORMAT)
+    protected Date create;
+    @JsonProperty("access_update")
+    @JsonFormat(pattern = DATE_FORMAT)
+    protected Date update;
+    @JsonProperty("access_creator")
+    protected String creator;
+
+    @Override
+    public String type() {
+        return HugeType.ACCESS.string();
+    }
+
+    @Override
+    public Date createTime() {
+        return this.create;
+    }
+
+    @Override
+    public Date updateTime() {
+        return this.update;
+    }
+
+    @Override
+    public String creator() {
+        return this.creator;
+    }
+
+    public Object group() {
+        return this.group;
+    }
+
+    public void group(Object group) {
+        if (group instanceof Group) {
+            group = ((Group) group).id();
+        }
+        this.group = group;
+    }
+
+    public Object target() {
+        return this.target;
+    }
+
+    public void target(Object target) {
+        if (target instanceof Target) {
+            target = ((Target) target).id();
+        }
+        this.target = target;
+    }
+
+    public HugePermission permission() {
+        return this.permission;
+    }
+
+    public void permission(HugePermission permission) {
+        this.permission = permission;
+    }
+
+    public String description() {
+        return this.description;
+    }
+
+    public void description(String description) {
+        this.description = description;
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/auth/AuthElement.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/auth/AuthElement.java
new file mode 100644
index 0000000..840aac3
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/auth/AuthElement.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.auth;
+
+import java.util.Date;
+
+import com.baidu.hugegraph.structure.Element;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public abstract class AuthElement extends Element {
+
+    public static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss.SSS";
+
+    @JsonProperty("id")
+    protected Object id;
+
+    @Override
+    public Object id() {
+        return this.id;
+    }
+
+    public abstract Date createTime();
+
+    public abstract Date updateTime();
+
+    public abstract String creator();
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/auth/Belong.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/auth/Belong.java
new file mode 100644
index 0000000..de0b072
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/auth/Belong.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.auth;
+
+import java.util.Date;
+
+import com.baidu.hugegraph.structure.constant.HugeType;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class Belong extends AuthElement {
+
+    @JsonProperty("user")
+    private Object user;
+    @JsonProperty("group")
+    private Object group;
+    @JsonProperty("belong_description")
+    private String description;
+
+    @JsonProperty("belong_create")
+    @JsonFormat(pattern = DATE_FORMAT)
+    protected Date create;
+    @JsonProperty("belong_update")
+    @JsonFormat(pattern = DATE_FORMAT)
+    protected Date update;
+    @JsonProperty("belong_creator")
+    protected String creator;
+
+    @Override
+    public String type() {
+        return HugeType.BELONG.string();
+    }
+
+    @Override
+    public Date createTime() {
+        return this.create;
+    }
+
+    @Override
+    public Date updateTime() {
+        return this.update;
+    }
+
+    @Override
+    public String creator() {
+        return this.creator;
+    }
+
+    public Object user() {
+        return this.user;
+    }
+
+    public void user(Object user) {
+        if (user instanceof User) {
+            user = ((User) user).id();
+        }
+        this.user = user;
+    }
+
+    public Object group() {
+        return this.group;
+    }
+
+    public void group(Object group) {
+        if (group instanceof Group) {
+            group = ((Group) group).id();
+        }
+        this.group = group;
+    }
+
+    public String description() {
+        return this.description;
+    }
+
+    public void description(String description) {
+        this.description = description;
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/auth/Group.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/auth/Group.java
new file mode 100644
index 0000000..ab53c9b
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/auth/Group.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.auth;
+
+import java.util.Date;
+
+import com.baidu.hugegraph.structure.constant.HugeType;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class Group extends AuthElement {
+
+    @JsonProperty("group_name")
+    private String name;
+    @JsonProperty("group_description")
+    private String description;
+
+    @JsonProperty("group_create")
+    @JsonFormat(pattern = DATE_FORMAT)
+    protected Date create;
+    @JsonProperty("group_update")
+    @JsonFormat(pattern = DATE_FORMAT)
+    protected Date update;
+    @JsonProperty("group_creator")
+    protected String creator;
+
+    @Override
+    public String type() {
+        return HugeType.GROUP.string();
+    }
+
+    @Override
+    public Date createTime() {
+        return this.create;
+    }
+
+    @Override
+    public Date updateTime() {
+        return this.update;
+    }
+
+    @Override
+    public String creator() {
+        return this.creator;
+    }
+
+    public String name() {
+        return this.name;
+    }
+
+    public void name(String name) {
+        this.name = name;
+    }
+
+    public String description() {
+        return this.description;
+    }
+
+    public void description(String description) {
+        this.description = description;
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/auth/HugePermission.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/auth/HugePermission.java
new file mode 100644
index 0000000..0bd4782
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/auth/HugePermission.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.auth;
+
+public enum HugePermission {
+
+    NONE(0x00),
+
+    READ(0x01),
+    WRITE(0x02),
+    DELETE(0x04),
+    EXECUTE(0x08),
+
+    ANY(0x7f);
+
+    private final byte code;
+
+    HugePermission(int code) {
+        assert code < 256;
+        this.code = (byte) code;
+    }
+
+    public byte code() {
+        return this.code;
+    }
+
+    public String string() {
+        return this.name().toLowerCase();
+    }
+
+    public boolean match(HugePermission other) {
+        if (other == ANY) {
+            return this == ANY;
+        }
+        return (this.code & other.code) != 0;
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/auth/HugeResource.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/auth/HugeResource.java
new file mode 100644
index 0000000..846d056
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/auth/HugeResource.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.auth;
+
+import java.util.Map;
+import java.util.Objects;
+
+import com.baidu.hugegraph.util.JsonUtil;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class HugeResource {
+
+    public static final String ANY = "*";
+
+    @JsonProperty("type")
+    private HugeResourceType type = HugeResourceType.NONE;
+
+    @JsonProperty("label")
+    private String label = ANY;
+
+    @JsonProperty("properties")
+    private Map<String, Object> properties; // value can be predicate
+
+    public HugeResource() {
+        // pass
+    }
+
+    public HugeResource(HugeResourceType type) {
+        this(type, ANY);
+    }
+
+    public HugeResource(HugeResourceType type, String label) {
+        this(type, label, null);
+    }
+
+    public HugeResource(HugeResourceType type, String label,
+                        Map<String, Object> properties) {
+        this.type = type;
+        this.label = label;
+        this.properties = properties;
+    }
+
+
+    public HugeResourceType resourceType() {
+        return this.type;
+    }
+
+    public String label() {
+        return this.label;
+    }
+
+    public Map<String, Object> properties() {
+        return this.properties;
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (!(object instanceof HugeResource)) {
+            return false;
+        }
+        HugeResource other = (HugeResource) object;
+        return this.type == other.type &&
+               Objects.equals(this.label, other.label) &&
+               Objects.equals(this.properties, other.properties);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(this.type, this.label, this.properties);
+    }
+
+    @Override
+    public String toString() {
+        return JsonUtil.toJson(this);
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/auth/HugeResourceType.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/auth/HugeResourceType.java
new file mode 100644
index 0000000..5c2de4b
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/auth/HugeResourceType.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.auth;
+
+public enum HugeResourceType {
+
+    NONE,
+
+    STATUS,
+
+    VERTEX,
+
+    EDGE,
+
+    VERTEX_AGGR,
+
+    EDGE_AGGR,
+
+    VAR,
+
+    GREMLIN,
+
+    TASK,
+
+    PROPERTY_KEY,
+
+    VERTEX_LABEL,
+
+    EDGE_LABEL,
+
+    INDEX_LABEL, // include create/rebuild/delete index
+
+    SCHEMA,
+
+    META,
+
+    ALL,
+
+    GRANT,
+
+    USER_GROUP,
+
+    PROJECT,
+
+    TARGET,
+
+    METRICS,
+
+    ROOT;
+
+    public boolean isGraph() {
+        int ord = this.ordinal();
+        return VERTEX.ordinal() <= ord && ord <= EDGE.ordinal();
+    }
+
+    public boolean isSchema() {
+        int ord = this.ordinal();
+        return PROPERTY_KEY.ordinal() <= ord && ord <= SCHEMA.ordinal();
+    }
+
+    public boolean isAuth() {
+        int ord = this.ordinal();
+        return GRANT.ordinal() <= ord && ord <= TARGET.ordinal();
+    }
+
+    public boolean isGrantOrUser() {
+        return this == GRANT || this == USER_GROUP;
+    }
+
+    public boolean isRepresentative() {
+        return this == ROOT || this == ALL || this == SCHEMA;
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/auth/Login.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/auth/Login.java
new file mode 100644
index 0000000..f98cd2e
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/auth/Login.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.auth;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class Login {
+
+    @JsonProperty("user_name")
+    private String name;
+
+    @JsonProperty("user_password")
+    private String password;
+
+    public void name(String name) {
+        this.name = name;
+    }
+
+    public String name() {
+        return this.name;
+    }
+
+    public void password(String password) {
+        this.password = password;
+    }
+
+    public String password() {
+        return this.password;
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/auth/LoginResult.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/auth/LoginResult.java
new file mode 100644
index 0000000..7f92df0
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/auth/LoginResult.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.auth;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class LoginResult {
+
+    @JsonProperty("token")
+    private String token;
+
+    public void token(String token) {
+        this.token = token;
+    }
+
+    public String token() {
+        return this.token;
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/auth/Project.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/auth/Project.java
new file mode 100644
index 0000000..754dcc9
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/auth/Project.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.auth;
+
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Set;
+
+import com.baidu.hugegraph.structure.constant.HugeType;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class Project extends AuthElement {
+
+    @JsonProperty("project_name")
+    private String name;
+    @JsonProperty("project_admin_group")
+    private String adminGroup;
+    @JsonProperty("project_op_group")
+    private String opGroup;
+    @JsonProperty("project_graphs")
+    private Set<String> graphs;
+    @JsonProperty("project_target")
+    private String target;
+    @JsonProperty("project_description")
+    private String description;
+
+    @JsonProperty("project_create")
+    @JsonFormat(pattern = DATE_FORMAT)
+    private Date create;
+    @JsonProperty("project_update")
+    @JsonFormat(pattern = DATE_FORMAT)
+    private Date update;
+    @JsonProperty("project_creator")
+    private String creator;
+
+    public Project() {
+    }
+
+    public Project(Object id) {
+        this(id, null, null);
+    }
+
+    public Project(String name) {
+        this(name, null);
+    }
+
+    public Project(String name, String description) {
+        this(null, name, description);
+    }
+
+    public Project(Object id, String name, String description) {
+        this.id = id;
+        this.name = name;
+        this.description = description;
+    }
+
+    public String name() {
+        return this.name;
+    }
+
+    public void name(String name) {
+        this.name = name;
+    }
+
+    public String adminGroup() {
+        return this.adminGroup;
+    }
+
+    public String opGroup() {
+        return this.opGroup;
+    }
+
+    public Set<String> graphs() {
+        return this.graphs;
+    }
+
+    public void graphs(Set<String> graphs) {
+        if (graphs != null) {
+            this.graphs = new HashSet<>(graphs);
+        } else {
+            this.graphs = null;
+        }
+    }
+
+    public String target() {
+        return this.target;
+    }
+
+    public String description() {
+        return this.description;
+    }
+
+    public void description(String description) {
+        this.description = description;
+    }
+
+    @Override
+    public String type() {
+        return HugeType.PROJECT.string();
+    }
+
+    @Override
+    public Date createTime() {
+        return this.create;
+    }
+
+    @Override
+    public Date updateTime() {
+        return this.update;
+    }
+
+    @Override
+    public String creator() {
+        return this.creator;
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/auth/Target.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/auth/Target.java
new file mode 100644
index 0000000..e2a8721
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/auth/Target.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.auth;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+
+import com.baidu.hugegraph.structure.constant.HugeType;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class Target extends AuthElement {
+
+    @JsonProperty("target_name")
+    private String name;
+    @JsonProperty("target_graph")
+    private String graph;
+    @JsonProperty("target_url")
+    private String url;
+    @JsonProperty("target_resources")
+    private List<HugeResource> resources;
+
+    @JsonProperty("target_create")
+    @JsonFormat(pattern = DATE_FORMAT)
+    protected Date create;
+    @JsonProperty("target_update")
+    @JsonFormat(pattern = DATE_FORMAT)
+    protected Date update;
+    @JsonProperty("target_creator")
+    protected String creator;
+
+    @Override
+    public String type() {
+        return HugeType.TARGET.string();
+    }
+
+    @Override
+    public Date createTime() {
+        return this.create;
+    }
+
+    @Override
+    public Date updateTime() {
+        return this.update;
+    }
+
+    @Override
+    public String creator() {
+        return this.creator;
+    }
+
+    public String name() {
+        return this.name;
+    }
+
+    public void name(String name) {
+        this.name = name;
+    }
+
+    public String graph() {
+        return this.graph;
+    }
+
+    public void graph(String graph) {
+        this.graph = graph;
+    }
+
+    public String url() {
+        return this.url;
+    }
+
+    public void url(String url) {
+        this.url = url;
+    }
+
+    public HugeResource resource() {
+        if (this.resources == null || this.resources.size() != 1) {
+            return null;
+        }
+        return this.resources.get(0);
+    }
+
+    public List<HugeResource> resources() {
+        if (this.resources == null) {
+            return null;
+        }
+        return Collections.unmodifiableList(this.resources);
+    }
+
+    public void resources(List<HugeResource> resources) {
+        this.resources = resources;
+    }
+
+    public void resources(HugeResource... resources) {
+        this.resources = Arrays.asList(resources);
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/auth/TokenPayload.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/auth/TokenPayload.java
new file mode 100644
index 0000000..925fa63
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/auth/TokenPayload.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.auth;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class TokenPayload {
+
+    @JsonProperty("user_id")
+    private String userId;
+
+    @JsonProperty("user_name")
+    private String username;
+
+    private TokenPayload() {
+    }
+
+    public String userId() {
+        return this.userId;
+    }
+
+    public void userId(String userId) {
+        this.userId = userId;
+    }
+
+    public String username() {
+        return this.username;
+    }
+
+    public void username(String username) {
+        this.username = username;
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/auth/User.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/auth/User.java
new file mode 100644
index 0000000..c06ebbd
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/auth/User.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.auth;
+
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+import com.baidu.hugegraph.structure.constant.HugeType;
+import com.baidu.hugegraph.util.JsonUtil;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class User extends AuthElement {
+
+    @JsonProperty("user_name")
+    private String name;
+    @JsonProperty("user_password")
+    private String password;
+    @JsonProperty("user_phone")
+    private String phone;
+    @JsonProperty("user_email")
+    private String email;
+    @JsonProperty("user_avatar")
+    private String avatar;
+    @JsonProperty("user_description")
+    private String description;
+
+    @JsonProperty("user_create")
+    @JsonFormat(pattern = DATE_FORMAT)
+    protected Date create;
+    @JsonProperty("user_update")
+    @JsonFormat(pattern = DATE_FORMAT)
+    protected Date update;
+    @JsonProperty("user_creator")
+    protected String creator;
+
+    @Override
+    public String type() {
+        return HugeType.USER.string();
+    }
+
+    @Override
+    public Date createTime() {
+        return this.create;
+    }
+
+    @Override
+    public Date updateTime() {
+        return this.update;
+    }
+
+    @Override
+    public String creator() {
+        return this.creator;
+    }
+
+    public String name() {
+        return this.name;
+    }
+
+    public void name(String name) {
+        this.name = name;
+    }
+
+    public String password() {
+        return this.password;
+    }
+
+    public void password(String password) {
+        this.password = password;
+    }
+
+    public String phone() {
+        return this.phone;
+    }
+
+    public void phone(String phone) {
+        this.phone = phone;
+    }
+
+    public String email() {
+        return this.email;
+    }
+
+    public void email(String email) {
+        this.email = email;
+    }
+
+    public String avatar() {
+        return this.avatar;
+    }
+
+    public void avatar(String avatar) {
+        this.avatar = avatar;
+    }
+
+    public String description() {
+        return this.description;
+    }
+
+    public void description(String description) {
+        this.description = description;
+    }
+
+    public static class UserRole {
+
+        @JsonProperty("roles")
+        private Map<String, Map<HugePermission, List<HugeResource>>> roles;
+
+        public Map<String, Map<HugePermission, List<HugeResource>>> roles() {
+            return Collections.unmodifiableMap(this.roles);
+        }
+
+        @Override
+        public String toString() {
+            return JsonUtil.toJson(this);
+        }
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/constant/AggregateType.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/constant/AggregateType.java
new file mode 100644
index 0000000..7b8bc5f
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/constant/AggregateType.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.constant;
+
+public enum AggregateType {
+
+    NONE(0, "none"),
+    MAX(1, "max"),
+    MIN(2, "min"),
+    SUM(3, "sum"),
+    OLD(4, "old");
+
+    private final byte code;
+    private final String name;
+
+    AggregateType(int code, String name) {
+        assert code < 256;
+        this.code = (byte) code;
+        this.name = name;
+    }
+
+    public byte code() {
+        return this.code;
+    }
+
+    public String string() {
+        return this.name;
+    }
+
+    public boolean isNone() {
+        return this == NONE;
+    }
+
+    public boolean isMax() {
+        return this == MAX;
+    }
+
+    public boolean isMin() {
+        return this == MIN;
+    }
+
+    public boolean isSum() {
+        return this == SUM;
+    }
+
+    public boolean isNumber() {
+        return this.isMax() || this.isMin() || this.isSum();
+    }
+
+    public boolean isOld() {
+        return this == OLD;
+    }
+
+    public boolean isIndexable() {
+        return this == NONE || this == MAX || this == MIN || this == OLD;
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/constant/Cardinality.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/constant/Cardinality.java
new file mode 100644
index 0000000..879b0af
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/constant/Cardinality.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.constant;
+
+public enum Cardinality {
+
+    SINGLE(1, "single"),
+
+    LIST(2, "list"),
+    
+    SET(3, "set");
+
+    // HugeKeys define
+    private byte code = 0;
+    private String name = null;
+
+    Cardinality(int code, String name) {
+        assert code < 256;
+        this.code = (byte) code;
+        this.name = name;
+    }
+
+    public byte code() {
+        return this.code;
+    }
+
+    public String string() {
+        return this.name;
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/constant/DataType.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/constant/DataType.java
new file mode 100644
index 0000000..380ea27
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/constant/DataType.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.constant;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.UUID;
+
+public enum DataType {
+
+    OBJECT(1, "object", Serializable.class),
+    BOOLEAN(2, "boolean", Boolean.class),
+    BYTE(3, "byte", Byte.class),
+    INT(4, "int", Integer.class),
+    LONG(5, "long", Long.class),
+    FLOAT(6, "float", Float.class),
+    DOUBLE(7, "double", Double.class),
+    TEXT(8, "text", String.class),
+    BLOB(9, "blob", byte[].class),
+    DATE(10, "date", Date.class),
+    UUID(11, "uuid", UUID.class);
+
+    private byte code = 0;
+    private String name = null;
+    private Class<?> clazz = null;
+
+    DataType(int code, String name, Class<?> clazz) {
+        assert code < 256;
+        this.code = (byte) code;
+        this.name = name;
+        this.clazz = clazz;
+    }
+
+    public byte code() {
+        return this.code;
+    }
+
+    public String string() {
+        return this.name;
+    }
+
+    public Class<?> clazz() {
+        return this.clazz;
+    }
+
+    public boolean isNumber() {
+        return this == BYTE || this == INT || this == LONG ||
+               this == FLOAT || this == DOUBLE;
+    }
+
+    public boolean isDate() {
+        return this == DataType.DATE;
+    }
+
+    public boolean isUUID() {
+        return this == DataType.UUID;
+    }
+
+    public boolean isBoolean() {
+        return this == BOOLEAN;
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/constant/Direction.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/constant/Direction.java
new file mode 100644
index 0000000..7d93c21
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/constant/Direction.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.constant;
+
+public enum Direction {
+
+    OUT(1, "out"),
+
+    IN(2, "in"),
+
+    BOTH(3, "both");
+
+    private byte code = 0;
+    private String name = null;
+
+    Direction(int code, String name) {
+        assert code < 256;
+        this.code = (byte) code;
+        this.name = name;
+    }
+
+    public byte code() {
+        return this.code;
+    }
+
+    public String string() {
+        return this.name;
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/constant/Frequency.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/constant/Frequency.java
new file mode 100644
index 0000000..6345143
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/constant/Frequency.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.constant;
+
+public enum Frequency {
+
+    DEFAULT(0, "default"),
+
+    SINGLE(1, "single"),
+
+    MULTIPLE(2, "multiple");
+
+    private byte code = 0;
+    private String name = null;
+
+    Frequency(int code, String name) {
+        assert code < 256;
+        this.code = (byte) code;
+        this.name = name;
+    }
+
+    public byte code() {
+        return this.code;
+    }
+
+    public String string() {
+        return this.name;
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/constant/GraphAttachable.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/constant/GraphAttachable.java
new file mode 100644
index 0000000..f87c105
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/constant/GraphAttachable.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.constant;
+
+import com.baidu.hugegraph.driver.GraphManager;
+
+public interface GraphAttachable {
+
+    public void attachManager(GraphManager manager);
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/constant/GraphMode.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/constant/GraphMode.java
new file mode 100644
index 0000000..594e738
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/constant/GraphMode.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.constant;
+
+public enum GraphMode {
+
+    /*
+     * None mode is regular mode
+     * 1. Not allowed create schema with specified id
+     * 2. Not support create vertex with id for AUTOMATIC id strategy
+     */
+    NONE(1, "none"),
+
+    /*
+     * Restoring mode is used to restore schema and graph data to an new graph.
+     * 1. Support create schema with specified id
+     * 2. Support create vertex with id for AUTOMATIC id strategy
+     */
+    RESTORING(2, "restoring"),
+
+    /*
+     * MERGING mode is used to merge schema and graph data to an existing graph.
+     * 1. Not allowed create schema with specified id
+     * 2. Support create vertex with id for AUTOMATIC id strategy
+     */
+    MERGING(3, "merging"),
+
+    /*
+     * LOADING mode used to load data via hugegraph-loader.
+     */
+    LOADING(4, "loading");
+
+    private final byte code;
+    private final String name;
+
+    GraphMode(int code, String name) {
+        assert code < 256;
+        this.code = (byte) code;
+        this.name = name;
+    }
+
+    public byte code() {
+        return this.code;
+    }
+
+    public String string() {
+        return this.name;
+    }
+
+    public boolean maintaining() {
+        return this == RESTORING || this == MERGING;
+    }
+
+    public boolean loading() {
+        return this == LOADING;
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/constant/GraphReadMode.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/constant/GraphReadMode.java
new file mode 100644
index 0000000..73fec49
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/constant/GraphReadMode.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.constant;
+
+public enum GraphReadMode {
+
+    ALL(1, "all"),
+
+    OLTP_ONLY(2, "oltp_only"),
+
+    OLAP_ONLY(3, "olap_only");
+
+    private final byte code;
+    private final String name;
+
+    private GraphReadMode(int code, String name) {
+        assert code < 256;
+        this.code = (byte) code;
+        this.name = name;
+    }
+
+    public byte code() {
+        return this.code;
+    }
+
+    public String string() {
+        return this.name;
+    }
+
+    public boolean showOlap() {
+        return this == ALL || this == OLAP_ONLY;
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/constant/HugeType.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/constant/HugeType.java
new file mode 100644
index 0000000..f505dc9
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/constant/HugeType.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.constant;
+
+public enum HugeType {
+
+    // Schema
+    VERTEX_LABEL(1, "vertexlabels"),
+    EDGE_LABEL(2, "edgelabels"),
+    PROPERTY_KEY(3, "propertykeys"),
+    INDEX_LABEL(4, "indexlabels"),
+
+    // Auth
+    TARGET(50, "targets"),
+    GROUP(51, "groups"),
+    USER(52, "users"),
+    ACCESS(53, "accesses"),
+    BELONG(54, "belongs"),
+    PROJECT(55, "projects"),
+    LOGIN(56, "login"),
+    LOGOUT(57, "logout"),
+    TOKEN_VERIFY(58, "verify"),
+
+    // Data
+    VERTEX(101, "vertices"),
+    EDGE(120, "edges"),
+
+    // Variables
+    VARIABLES(130, "variables"),
+
+    // Task
+    TASK(140, "tasks"),
+
+    // Job
+    JOB(150, "jobs"),
+
+    // Gremlin
+    GREMLIN(201, "gremlin"),
+
+    GRAPHS(220, "graphs"),
+
+    // Version
+    VERSION(230, "versions"),
+
+    // Metrics
+    METRICS(240, "metrics");
+
+    private int code;
+    private String name = null;
+
+    HugeType(int code, String name) {
+        assert code < 256;
+        this.code = code;
+        this.name = name;
+    }
+
+    public int code() {
+        return this.code;
+    }
+
+    public String string() {
+        return this.name;
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/constant/IdStrategy.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/constant/IdStrategy.java
new file mode 100644
index 0000000..4d4280a
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/constant/IdStrategy.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.constant;
+
+public enum IdStrategy {
+
+    DEFAULT(0, "default"),
+
+    AUTOMATIC(1, "automatic"),
+
+    PRIMARY_KEY(2, "primary_key"),
+
+    CUSTOMIZE_STRING(3, "customize_string"),
+
+    CUSTOMIZE_NUMBER(4, "customize_number"),
+
+    CUSTOMIZE_UUID(5, "customize_uuid");
+
+    private byte code = 0;
+    private String name = null;
+
+    IdStrategy(int code, String name) {
+        assert code < 256;
+        this.code = (byte) code;
+        this.name = name;
+    }
+
+    public byte code() {
+        return this.code;
+    }
+
+    public String string() {
+        return this.name;
+    }
+
+    public boolean isAutomatic() {
+        return this == IdStrategy.AUTOMATIC;
+    }
+
+    public boolean isCustomize() {
+        return this == IdStrategy.CUSTOMIZE_STRING ||
+               this == IdStrategy.CUSTOMIZE_NUMBER ||
+               this == IdStrategy.CUSTOMIZE_UUID;
+    }
+
+    public boolean isCustomizeString() {
+        return this == IdStrategy.CUSTOMIZE_STRING;
+    }
+
+    public boolean isCustomizeNumber() {
+        return this == IdStrategy.CUSTOMIZE_NUMBER;
+    }
+
+    public boolean isCustomizeUuid() {
+        return this == IdStrategy.CUSTOMIZE_UUID;
+    }
+
+    public boolean isPrimaryKey() {
+        return this == IdStrategy.PRIMARY_KEY;
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/constant/IndexType.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/constant/IndexType.java
new file mode 100644
index 0000000..9b9bbe8
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/constant/IndexType.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.constant;
+
+public enum IndexType {
+
+    // For secondary query
+    SECONDARY(1, "secondary"),
+
+    // For range query
+    RANGE(2, "range"),
+
+    // For full-text query (not supported now)
+    SEARCH(3, "search"),
+
+    // For prefix + range query
+    SHARD(4, "shard"),
+
+    // For unique properties
+    UNIQUE(5, "unique");
+
+    private byte code = 0;
+    private String name = null;
+
+    IndexType(int code, String name) {
+        assert code < 256;
+        this.code = (byte) code;
+        this.name = name;
+    }
+
+    public byte code() {
+        return this.code;
+    }
+
+    public String string() {
+        return this.name;
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/constant/T.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/constant/T.java
new file mode 100644
index 0000000..f121662
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/constant/T.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.constant;
+
+public class T {
+
+    public static final String id = "id";
+    public static final String label = "label";
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/constant/Traverser.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/constant/Traverser.java
new file mode 100644
index 0000000..791e5ee
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/constant/Traverser.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.constant;
+
+public class Traverser {
+
+    public static final long DEFAULT_CAPACITY = 10_000_000L;
+    public static final long DEFAULT_LIMIT = 100L;
+    public static final long DEFAULT_ELEMENTS_LIMIT = 10_000_000L;
+    public static final long DEFAULT_MAX_DEGREE = 10_000L;
+    public static final long DEFAULT_CROSSPOINT_LIMIT = 10_000L;
+    public static final long DEFAULT_PATHS_LIMIT = 10L;
+    public static final long DEFAULT_DEDUP_SIZE = 1_000_000L;
+    public static final long DEFAULT_SKIP_DEGREE = 100_000L;
+    public static final long DEFAULT_SAMPLE = 100L;
+    public static final double DEFAULT_WEIGHT = 0.0D;
+    public static final long DEFAULT_PAGE_LIMIT = 100_000L;
+    public static final double DEFAULT_ALPHA = 0.9;
+    public static final int DEFAULT_MAX_TOP = 1000;
+    public static final int DEFAULT_MAX_DEPTH = 5000;
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/constant/WriteType.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/constant/WriteType.java
new file mode 100644
index 0000000..5052033
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/constant/WriteType.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.constant;
+
+public enum WriteType {
+
+    // OLTP property key
+    OLTP(1, "oltp"),
+
+    // OLAP property key without index
+    OLAP_COMMON(2, "olap_common"),
+
+    // OLAP property key with secondary index
+    OLAP_SECONDARY(3, "olap_secondary"),
+
+    // OLAP property key with range index
+    OLAP_RANGE(4, "olap_range");
+
+    private byte code = 0;
+    private String name = null;
+
+    WriteType(int code, String name) {
+        assert code < 256;
+        this.code = (byte) code;
+        this.name = name;
+    }
+
+    public byte code() {
+        return this.code;
+    }
+
+    public String string() {
+        return this.name;
+    }
+
+    public boolean oltp() {
+        return this == OLTP;
+    }
+
+    public boolean olap() {
+        return this == OLAP_COMMON ||
+               this == OLAP_RANGE ||
+               this == OLAP_SECONDARY;
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/graph/BatchEdgeRequest.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/graph/BatchEdgeRequest.java
new file mode 100644
index 0000000..b06703c
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/graph/BatchEdgeRequest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.graph;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.baidu.hugegraph.util.E;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class BatchEdgeRequest {
+
+    @JsonProperty("edges")
+    private List<Edge> edges;
+    @JsonProperty("update_strategies")
+    private Map<String, UpdateStrategy> updateStrategies;
+    @JsonProperty("check_vertex")
+    private boolean checkVertex;
+    @JsonProperty("create_if_not_exist")
+    private boolean createIfNotExist;
+
+    public BatchEdgeRequest() {
+        this.edges = null;
+        this.updateStrategies = null;
+        this.checkVertex = false;
+        this.createIfNotExist = true;
+    }
+
+    public static Builder createBuilder() {
+        return new Builder();
+    }
+
+    @Override
+    public String toString() {
+        return String.format("BatchEdgeRequest{edges=%s," +
+                             "updateStrategies=%s," +
+                             "checkVertex=%s,createIfNotExist=%s}",
+                             this.edges, this.updateStrategies,
+                             this.checkVertex, this.createIfNotExist);
+    }
+
+    public static class Builder {
+
+        private BatchEdgeRequest req;
+
+        public Builder() {
+            this.req = new BatchEdgeRequest();
+        }
+
+        public Builder edges(List<Edge> edges) {
+            this.req.edges = edges;
+            return this;
+        }
+
+        public Builder updatingStrategies(Map<String, UpdateStrategy> map) {
+            this.req.updateStrategies = new HashMap<>(map);
+            return this;
+        }
+
+        public Builder updatingStrategy(String property,
+                                        UpdateStrategy strategy) {
+            this.req.updateStrategies.put(property, strategy);
+            return this;
+        }
+
+        public Builder checkVertex(boolean checkVertex) {
+            this.req.checkVertex = checkVertex;
+            return this;
+        }
+
+        public Builder createIfNotExist(boolean createIfNotExist) {
+            this.req.createIfNotExist = createIfNotExist;
+            return this;
+        }
+
+        public BatchEdgeRequest build() {
+            E.checkArgumentNotNull(req, "BatchEdgeRequest can't be null");
+            E.checkArgumentNotNull(req.edges,
+                                   "Parameter 'edges' can't be null");
+            E.checkArgument(req.updateStrategies != null &&
+                            !req.updateStrategies.isEmpty(),
+                            "Parameter 'update_strategies' can't be empty");
+            E.checkArgument(req.createIfNotExist == true,
+                            "Parameter 'create_if_not_exist' " +
+                            "dose not support false now");
+            return this.req;
+        }
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/graph/BatchOlapPropertyRequest.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/graph/BatchOlapPropertyRequest.java
new file mode 100644
index 0000000..2eda5be
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/graph/BatchOlapPropertyRequest.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.graph;
+
+import java.util.List;
+
+import com.baidu.hugegraph.util.E;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class BatchOlapPropertyRequest {
+
+    @JsonProperty("vertices")
+    private List<OlapVertex> vertices;
+    @JsonProperty("property_key")
+    private String propertyKey;
+
+    public BatchOlapPropertyRequest() {
+        this.vertices = null;
+        this.propertyKey = null;
+    }
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    @Override
+    public String toString() {
+        return String.format("BatchOlapPropertyRequest{vertices=%s," +
+                             "propertyKey=%s}",
+                             this.vertices, this.propertyKey);
+    }
+
+    public static class Builder {
+
+        private BatchOlapPropertyRequest req;
+
+        private Builder() {
+            this.req = new BatchOlapPropertyRequest();
+        }
+
+        public Builder vertices(List<OlapVertex> vertices) {
+            E.checkArgument(vertices != null && !vertices.isEmpty(),
+                            "Parameter 'vertices' can't be null or empty");
+            this.req.vertices = vertices;
+            return this;
+        }
+
+        public Builder propertyKey(String propertyKey) {
+            E.checkArgumentNotNull(propertyKey,
+                                   "The property key can't be null");
+            this.req.propertyKey = propertyKey;
+            return this;
+        }
+
+        public BatchOlapPropertyRequest build() {
+            E.checkArgumentNotNull(req,
+                                   "BatchOlapPropertyRequest can't be null");
+            E.checkArgumentNotNull(req.vertices,
+                                   "Parameter 'vertices' can't be null");
+            E.checkArgument(req.propertyKey != null,
+                            "Parameter 'property_key' can't be empty");
+            return this.req;
+        }
+    }
+
+    public static class OlapVertex {
+
+        @JsonProperty("id")
+        public Object id;
+        @JsonProperty("label")
+        public String label;
+        @JsonProperty("value")
+        public Object value;
+
+        @Override
+        public String toString() {
+            return String.format("OlapVertex{id=%s,label=%s,value=%s}",
+                                 this.id, this.label, this.value);
+        }
+
+        public static class Builder {
+
+            private OlapVertex vertex;
+
+            public Builder() {
+                this.vertex = new OlapVertex();
+            }
+
+            public Builder id(Object id) {
+                E.checkArgumentNotNull(id, "The id of vertex can't be null");
+                this.vertex.id = id;
+                return this;
+            }
+
+            public Builder label(String label) {
+                E.checkArgumentNotNull(label,
+                                       "The label of vertex can't be null");
+                this.vertex.label = label;
+                return this;
+            }
+
+            public Builder value(Object value) {
+                E.checkArgumentNotNull(value,
+                                       "The value of vertex can't be null");
+                this.vertex.value = value;
+                return this;
+            }
+
+            public OlapVertex build() {
+                E.checkArgumentNotNull(this.vertex.id,
+                                       "The id of vertex can't be null");
+                E.checkArgumentNotNull(this.vertex.label,
+                                       "The label of vertex can't be null");
+                E.checkArgumentNotNull(this.vertex.value,
+                                       "The value of vertex can't be null");
+                return this.vertex;
+            }
+        }
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/graph/BatchVertexRequest.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/graph/BatchVertexRequest.java
new file mode 100644
index 0000000..ca57d62
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/graph/BatchVertexRequest.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.graph;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.baidu.hugegraph.util.E;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class BatchVertexRequest {
+
+    @JsonProperty("vertices")
+    private List<Vertex> vertices;
+    @JsonProperty("update_strategies")
+    private Map<String, UpdateStrategy> updateStrategies;
+    @JsonProperty("create_if_not_exist")
+    private boolean createIfNotExist;
+
+    public BatchVertexRequest() {
+        this.vertices = null;
+        this.updateStrategies = null;
+        this.createIfNotExist = true;
+    }
+
+    public static Builder createBuilder() {
+        return new Builder();
+    }
+
+    @Override
+    public String toString() {
+        return String.format("BatchVertexRequest{vertices=%s," +
+                             "updateStrategies=%s,createIfNotExist=%s}",
+                             this.vertices, this.updateStrategies,
+                             this.createIfNotExist);
+    }
+
+    public static class Builder {
+
+        private BatchVertexRequest req;
+
+        public Builder() {
+            this.req = new BatchVertexRequest();
+        }
+
+        public Builder vertices(List<Vertex> vertices) {
+            this.req.vertices = vertices;
+            return this;
+        }
+
+        public Builder updatingStrategies(Map<String, UpdateStrategy> map) {
+            this.req.updateStrategies = new HashMap<>(map);
+            return this;
+        }
+
+        public Builder updatingStrategy(String property,
+                                        UpdateStrategy strategy) {
+            this.req.updateStrategies.put(property, strategy);
+            return this;
+        }
+
+        public Builder createIfNotExist(boolean createIfNotExist) {
+            this.req.createIfNotExist = createIfNotExist;
+            return this;
+        }
+
+        public BatchVertexRequest build() {
+            E.checkArgumentNotNull(req, "BatchVertexRequest can't be null");
+            E.checkArgumentNotNull(req.vertices,
+                                   "Parameter 'vertices' can't be null");
+            E.checkArgument(req.updateStrategies != null &&
+                            !req.updateStrategies.isEmpty(),
+                            "Parameter 'update_strategies' can't be empty");
+            E.checkArgument(req.createIfNotExist == true,
+                            "Parameter 'create_if_not_exist' " +
+                            "dose not support false now");
+            return this.req;
+        }
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/graph/Edge.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/graph/Edge.java
new file mode 100644
index 0000000..6d6c0df
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/graph/Edge.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.graph;
+
+import com.baidu.hugegraph.exception.InvalidOperationException;
+import com.baidu.hugegraph.structure.GraphElement;
+import com.baidu.hugegraph.util.E;
+import com.baidu.hugegraph.util.SplicingIdGenerator;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class Edge extends GraphElement {
+
+    @JsonProperty("id")
+    private String id;
+    @JsonProperty("outV")
+    private Object sourceId;
+    @JsonProperty("inV")
+    private Object targetId;
+    @JsonProperty("outVLabel")
+    private String sourceLabel;
+    @JsonProperty("inVLabel")
+    private String targetLabel;
+
+    private Vertex source;
+    private Vertex target;
+    private String name;
+
+    @JsonCreator
+    public Edge(@JsonProperty("label") String label) {
+        this.label = label;
+        this.type = "edge";
+        this.sourceId = null;
+        this.targetId = null;
+        this.sourceLabel = null;
+        this.targetLabel = null;
+        this.source = null;
+        this.target = null;
+        this.name = null;
+    }
+
+    public String id() {
+        return this.id;
+    }
+
+    public void id(String id) {
+        this.id = id;
+    }
+
+    public Object sourceId() {
+        if (this.sourceId == null && this.source != null) {
+            this.sourceId = this.source.id();
+        }
+        if (this.sourceId == null) {
+            throw new InvalidOperationException(
+                      "Must set source vertex id or add vertices " +
+                      "before add edges");
+        }
+        return this.sourceId;
+    }
+
+    public void sourceId(Object sourceId) {
+        E.checkArgumentNotNull(sourceId, "The source vertex id can't be null");
+        this.sourceId = sourceId;
+    }
+
+    public Edge source(Vertex source) {
+        if (source.id() == null) {
+            this.source = source;
+        }
+        this.sourceId = source.id();
+        this.sourceLabel = source.label();
+        return this;
+    }
+
+    public Object targetId() {
+        if (this.targetId == null && this.target != null) {
+            this.targetId = this.target.id();
+        }
+        if (this.targetId == null) {
+            throw new InvalidOperationException(
+                      "Must set target vertex id or add vertices " +
+                      "before add edges");
+        }
+        return this.targetId;
+    }
+
+    public void targetId(Object targetId) {
+        E.checkArgumentNotNull(targetId, "The target vertex id can't be null");
+        this.targetId = targetId;
+    }
+
+    public Edge target(Vertex target) {
+        if (target.id() == null) {
+            this.target = target;
+        }
+        this.targetId = target.id();
+        this.targetLabel = target.label();
+        return this;
+    }
+
+    public boolean linkedVertex(Object vertexId) {
+        return this.sourceId.equals(vertexId) ||
+               this.targetId.equals(vertexId);
+    }
+
+    public boolean linkedVertex(Vertex vertex) {
+        return this.linkedVertex(vertex.id());
+    }
+
+    public String sourceLabel() {
+        return this.sourceLabel;
+    }
+
+    public void sourceLabel(String sourceLabel) {
+        this.sourceLabel = sourceLabel;
+    }
+
+    public String targetLabel() {
+        return this.targetLabel;
+    }
+
+    public void targetLabel(String targetLabel) {
+        this.targetLabel = targetLabel;
+    }
+
+    public String name() {
+        if (this.name == null) {
+            String[] idParts = SplicingIdGenerator.split(this.id);
+            E.checkState(idParts.length == 4,
+                         "The edge id must be formatted by 4 parts, " +
+                         "actual is %s", idParts.length);
+            this.name = idParts[2];
+        }
+        return this.name;
+    }
+
+    @Override
+    public Edge property(String key, Object value) {
+        E.checkNotNull(key, "The property name can not be null");
+        E.checkNotNull(value, "The property value can not be null");
+        if (this.fresh()) {
+            return (Edge) super.property(key, value);
+        } else {
+            return this.setProperty(key, value);
+        }
+    }
+
+    @Override
+    protected Edge setProperty(String key, Object value) {
+        Edge edge = new Edge(this.label);
+        edge.id(this.id);
+        edge.sourceId(this.sourceId);
+        edge.targetId(this.targetId);
+        edge.property(key, value);
+        // NOTE: append can also be used to update property
+        edge = this.manager.appendEdgeProperty(edge);
+
+        super.property(key, edge.property(key));
+        return this;
+    }
+
+    @Override
+    public Edge removeProperty(String key) {
+        E.checkNotNull(key, "The property name can not be null");
+        if (!this.properties.containsKey(key)) {
+            throw new InvalidOperationException(
+                      "The edge '%s' doesn't have the property '%s'",
+                      this.id, key);
+        }
+        Edge edge = new Edge(this.label);
+        edge.id(this.id);
+        edge.sourceId(this.sourceId);
+        edge.targetId(this.targetId);
+
+        Object value = this.properties.get(key);
+        edge.property(key, value);
+        this.manager.eliminateEdgeProperty(edge);
+
+        this.properties().remove(key);
+        return this;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("{id=%s, sourceId=%s, sourceLabel=%s, " +
+                             "targetId=%s, targetLabel=%s, " +
+                             "label=%s, properties=%s}",
+                this.id, this.sourceId, this.sourceLabel,
+                this.targetId, this.targetLabel,
+                this.label, this.properties);
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/graph/Edges.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/graph/Edges.java
new file mode 100644
index 0000000..fe824a7
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/graph/Edges.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.graph;
+
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class Edges extends Pageable<Edge> {
+
+    @JsonProperty
+    private List<Edge> edges;
+
+    @JsonCreator
+    public Edges(@JsonProperty("edges") List<Edge> edges,
+                 @JsonProperty("page") String page) {
+        super(page);
+        this.edges = edges;
+    }
+
+    @Override
+    public List<Edge> results() {
+        return this.edges;
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/graph/Graph.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/graph/Graph.java
new file mode 100644
index 0000000..c682a0c
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/graph/Graph.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.graph;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.slf4j.Logger;
+
+import com.baidu.hugegraph.driver.GraphManager;
+import com.baidu.hugegraph.util.Log;
+
+/**
+ * HugeGraph is a mirror of server-side data(vertex/edge), it used to speed up
+ * data access. Note, however, the memory can't hold large amounts of data.
+ */
+public class Graph {
+
+    private static final Logger LOG = Log.logger(Graph.class);
+
+    private Map<Object, HugeVertex> hugeVerticesMap;
+    private List<HugeEdge> hugeEdges;
+
+    public Graph(GraphManager graph) {
+        LOG.debug("Loading Graph...");
+
+        List<Vertex> vertices = graph.listVertices();
+        LOG.debug("Loaded vertices: {}", vertices.size());
+
+        List<Edge> edges = graph.listEdges();
+        LOG.debug("Loaded edges: {}", edges.size());
+
+        this.mergeEdges2Vertices(vertices, edges);
+        LOG.debug("Loaded Graph");
+    }
+
+    public Graph(List<Vertex> vertices, List<Edge> edges) {
+        this.mergeEdges2Vertices(vertices, edges);
+    }
+
+    public Iterator<HugeVertex> vertices() {
+        return this.hugeVerticesMap.values().iterator();
+    }
+
+    public HugeVertex vertex(Object id) {
+        return this.hugeVerticesMap.get(id);
+    }
+
+    public Iterator<HugeEdge> edges() {
+        return this.hugeEdges.iterator();
+    }
+
+    private void mergeEdges2Vertices(List<Vertex> vertices,
+                                     List<Edge> edges) {
+        this.hugeVerticesMap = new HashMap<>(vertices.size());
+        for (Vertex v : vertices) {
+            this.hugeVerticesMap.put(v.id(), new HugeVertex(v));
+        }
+
+        this.hugeEdges = new ArrayList<>(edges.size());
+        for (Edge e : edges) {
+            HugeVertex src = this.hugeVerticesMap.get(e.sourceId());
+            HugeVertex tgt = this.hugeVerticesMap.get(e.targetId());
+
+            HugeEdge edge = new HugeEdge(e);
+            edge.source(src);
+            edge.target(tgt);
+
+            src.addEdge(edge);
+            tgt.addEdge(edge);
+
+            this.hugeEdges.add(edge);
+        }
+    }
+
+    public static class HugeVertex {
+
+        private Vertex vertex;
+        private List<HugeEdge> edges;
+
+        public HugeVertex(Vertex v) {
+            this.vertex = v;
+            this.edges = new ArrayList<>();
+        }
+
+        public Vertex vertex() {
+            return this.vertex;
+        }
+
+        public void vertex(Vertex vertex) {
+            this.vertex = vertex;
+        }
+
+        public void addEdge(HugeEdge e) {
+            this.edges.add(e);
+        }
+
+        public List<HugeEdge> getEdges() {
+            return this.edges;
+        }
+
+        @Override
+        public String toString() {
+            return String.format("HugeVertex{vertex=%s, edges=%s}",
+                                 this.vertex, this.edges);
+        }
+    }
+
+    public static class HugeEdge {
+
+        private Edge edge;
+        private HugeVertex source;
+        private HugeVertex target;
+
+        public HugeEdge(Edge e) {
+            this.edge = e;
+        }
+
+        public Edge edge() {
+            return this.edge;
+        }
+
+        public HugeVertex source() {
+            return this.source;
+        }
+
+        public void source(HugeVertex source) {
+            this.source = source;
+        }
+
+        public HugeVertex target() {
+            return this.target;
+        }
+
+        public void target(HugeVertex target) {
+            this.target = target;
+        }
+
+        public HugeVertex other(HugeVertex vertex) {
+            return vertex == this.source ? this.target : this.source;
+        }
+
+        @Override
+        public String toString() {
+            return String.format("HugeEdge{edge=%s, sourceId=%s, targetId=%s}",
+                                 this.edge,
+                                 this.source.vertex(),
+                                 this.target.vertex());
+        }
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/graph/GraphIterator.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/graph/GraphIterator.java
new file mode 100644
index 0000000..7394bb1
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/graph/GraphIterator.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.graph;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.function.Function;
+
+import com.baidu.hugegraph.driver.GraphManager;
+import com.baidu.hugegraph.structure.GraphElement;
+import com.baidu.hugegraph.util.E;
+
+public class GraphIterator<T extends GraphElement> implements Iterator<T> {
+
+    private final GraphManager graphManager;
+    private final int sizePerPage;
+    private final Function<String, Pageable<T>> pageFetcher;
+    private List<T> results;
+    private String page;
+    private int cursor;
+    private boolean finished;
+
+    public GraphIterator(final GraphManager graphManager, final int sizePerPage,
+                         final Function<String, Pageable<T>> pageFetcher) {
+        E.checkNotNull(graphManager, "Graph manager");
+        E.checkNotNull(pageFetcher, "Page fetcher");
+        this.graphManager = graphManager;
+        this.sizePerPage = sizePerPage;
+        this.pageFetcher = pageFetcher;
+        this.results = null;
+        this.page = "";
+        this.cursor = 0;
+        this.finished = false;
+    }
+
+    @Override
+    public boolean hasNext() {
+        if (this.results == null || this.cursor >= this.results.size()) {
+            this.fetch();
+        }
+        assert this.results != null;
+        return this.cursor < this.results.size();
+    }
+
+    private void fetch() {
+        if (this.finished) {
+            return;
+        }
+        Pageable<T> pageable = this.pageFetcher.apply(this.page);
+        this.results = pageable.results();
+        this.page = pageable.page();
+        this.cursor = 0;
+        E.checkState(this.results.size() <= this.sizePerPage,
+                     "Server returned unexpected results: %s > %s",
+                     this.results.size(), this.sizePerPage);
+        if (this.results.size() < this.sizePerPage || this.page == null) {
+            this.finished = true;
+        }
+    }
+
+    @Override
+    public T next() {
+        if (!this.hasNext()) {
+            throw new NoSuchElementException();
+        }
+
+        T elem = this.results.get(this.cursor++);
+        E.checkState(elem != null,
+                     "The server data is invalid, some records are null");
+        elem.attachManager(this.graphManager);
+        return elem;
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/graph/Pageable.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/graph/Pageable.java
new file mode 100644
index 0000000..83ba273
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/graph/Pageable.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.graph;
+
+import java.util.List;
+
+import com.baidu.hugegraph.structure.GraphElement;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public abstract class Pageable<T extends GraphElement> {
+
+    @JsonProperty
+    private String page;
+
+    @JsonCreator
+    public Pageable(@JsonProperty("page") String page) {
+        this.page = page;
+    }
+
+    public abstract List<T> results();
+
+    public String page() {
+        return this.page;
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/graph/Path.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/graph/Path.java
new file mode 100644
index 0000000..546bbfc
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/graph/Path.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.graph;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import com.baidu.hugegraph.driver.GraphManager;
+import com.baidu.hugegraph.structure.constant.GraphAttachable;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.collect.ImmutableList;
+
+public class Path implements GraphAttachable {
+
+    @JsonProperty
+    private List<Object> labels;
+    @JsonProperty
+    private List<Object> objects;
+    @JsonProperty
+    private Object crosspoint;
+
+    public Path() {
+        this(ImmutableList.of());
+    }
+
+    public Path(List<Object> objects) {
+        this(null, objects);
+    }
+
+    public Path(Object crosspoint, List<Object> objects) {
+        this.crosspoint = crosspoint;
+        this.labels = new CopyOnWriteArrayList<>();
+        this.objects = new CopyOnWriteArrayList<>(objects);
+    }
+
+    public List<Object> labels() {
+        return Collections.unmodifiableList(this.labels);
+    }
+
+    public void labels(Object... labels) {
+        this.labels.addAll(Arrays.asList(labels));
+    }
+
+    public List<Object> objects() {
+        return Collections.unmodifiableList(this.objects);
+    }
+
+    public void objects(Object... objects) {
+        this.objects.addAll(Arrays.asList(objects));
+    }
+
+    public Object crosspoint() {
+        return this.crosspoint;
+    }
+
+    public void crosspoint(Object crosspoint) {
+        this.crosspoint = crosspoint;
+    }
+
+    public int size() {
+        return this.objects.size();
+    }
+
+    @Override
+    public void attachManager(GraphManager manager) {
+        for (Object object : this.objects) {
+            if (object instanceof GraphAttachable) {
+                ((GraphAttachable) object).attachManager(manager);
+            }
+        }
+        if (this.crosspoint instanceof GraphAttachable) {
+            ((GraphAttachable) this.crosspoint).attachManager(manager);
+        }
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (!(object instanceof Path)) {
+            return false;
+        }
+        Path other = (Path) object;
+        return Objects.equals(this.labels, other.labels) &&
+               Objects.equals(this.objects, other.objects) &&
+               Objects.equals(this.crosspoint, other.crosspoint);
+    }
+
+    @Override
+    public String toString() {
+        return String.format("{labels=%s, objects=%s, crosspoint=%s}",
+                             this.labels, this.objects, this.crosspoint);
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/graph/Shard.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/graph/Shard.java
new file mode 100644
index 0000000..295d6e6
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/graph/Shard.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.graph;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * Shard is used for backend storage (like cassandra, hbase) scanning
+ * operations. Each shard represents a range of tokens for a node.
+ * Reading data from a given shard does not cross multiple nodes.
+ */
+public class Shard {
+
+    @JsonProperty("start")
+    private String start;
+    @JsonProperty("end")
+    private String end;
+    @JsonProperty("length")
+    private long length;
+
+    public Shard() {
+    }
+
+    public Shard(String start, String end, long length) {
+        this.start = start;
+        this.end = end;
+        this.length = length;
+    }
+
+    public String start() {
+        return this.start;
+    }
+
+    public void start(String start) {
+        this.start = start;
+    }
+
+    public String end() {
+        return this.end;
+    }
+
+    public void end(String end) {
+        this.end = end;
+    }
+
+    public long length() {
+        return this.length;
+    }
+
+    public void length(long length) {
+        this.length = length;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("Shard{start=%s, end=%s, length=%s}",
+                             this.start, this.end, this.length);
+    }
+}
\ No newline at end of file
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/graph/UpdateStrategy.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/graph/UpdateStrategy.java
new file mode 100644
index 0000000..55a8001
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/graph/UpdateStrategy.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.graph;
+
+public enum UpdateStrategy {
+
+    SUM,
+
+    BIGGER,
+
+    SMALLER,
+
+    UNION,
+
+    INTERSECTION,
+
+    APPEND,
+
+    ELIMINATE,
+
+    OVERRIDE
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/graph/Vertex.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/graph/Vertex.java
new file mode 100644
index 0000000..0322410
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/graph/Vertex.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.graph;
+
+import java.util.Map;
+
+import com.baidu.hugegraph.exception.InvalidOperationException;
+import com.baidu.hugegraph.structure.GraphElement;
+import com.baidu.hugegraph.util.E;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class Vertex extends GraphElement {
+
+    @JsonProperty("id")
+    private Object id;
+
+    @JsonCreator
+    public Vertex(@JsonProperty("label") String label) {
+        this.label = label;
+        this.type = "vertex";
+    }
+
+    public Object id() {
+        return this.id;
+    }
+
+    public void id(Object id) {
+        this.id = id;
+    }
+
+    public Edge addEdge(String label, Vertex vertex, Object... properties) {
+        E.checkNotNull(label, "The edge label can not be null.");
+        E.checkNotNull(vertex, "The target vertex can not be null.");
+        return this.manager.addEdge(this, label, vertex, properties);
+    }
+
+    public Edge addEdge(String label, Vertex vertex,
+                        Map<String, Object> properties) {
+        E.checkNotNull(label, "The edge label can not be null.");
+        E.checkNotNull(vertex, "The target vertex can not be null.");
+        return this.manager.addEdge(this, label, vertex, properties);
+    }
+
+    @Override
+    public Vertex property(String key, Object value) {
+        E.checkNotNull(key, "The property name can not be null");
+        E.checkNotNull(value, "The property value can not be null");
+        if (this.fresh()) {
+            return (Vertex) super.property(key, value);
+        } else {
+            return this.setProperty(key, value);
+        }
+    }
+
+    @Override
+    protected Vertex setProperty(String key, Object value) {
+        Vertex vertex = new Vertex(this.label);
+        vertex.id(this.id);
+        vertex.property(key, value);
+        // NOTE: append can also be used to update property
+        vertex = this.manager.appendVertexProperty(vertex);
+
+        super.property(key, vertex.property(key));
+        return this;
+    }
+
+    @Override
+    public Vertex removeProperty(String key) {
+        E.checkNotNull(key, "The property name can not be null");
+        if (!this.properties.containsKey(key)) {
+            throw new InvalidOperationException(
+                      "The vertex '%s' doesn't have the property '%s'",
+                      this.id, key);
+        }
+        Vertex vertex = new Vertex(this.label);
+        vertex.id(this.id);
+        Object value = this.properties.get(key);
+        vertex.property(key, value);
+        this.manager.eliminateVertexProperty(vertex);
+
+        this.properties().remove(key);
+        return this;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("{id=%s, label=%s, properties=%s}",
+                             this.id, this.label, this.properties);
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/graph/Vertices.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/graph/Vertices.java
new file mode 100644
index 0000000..10ae842
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/graph/Vertices.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.graph;
+
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class Vertices extends Pageable<Vertex> {
+
+    @JsonProperty
+    private List<Vertex> vertices;
+
+    @JsonCreator
+    public Vertices(@JsonProperty("vertices") List<Vertex> vertices,
+                    @JsonProperty("page") String page) {
+        super(page);
+        this.vertices = vertices;
+    }
+
+    @Override
+    public List<Vertex> results() {
+        return this.vertices;
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/gremlin/Response.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/gremlin/Response.java
new file mode 100644
index 0000000..901b687
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/gremlin/Response.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.gremlin;
+
+import java.util.Map;
+
+import com.baidu.hugegraph.driver.GraphManager;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class Response {
+
+    public static class Status {
+        @JsonProperty
+        private String message;
+        @JsonProperty
+        private int code;
+        @JsonProperty
+        private Map<String, ?> attributes;
+
+        public String message() {
+            return message;
+        }
+
+        public int code() {
+            return code;
+        }
+
+        public Map<String, ?> attributes() {
+            return attributes;
+        }
+    }
+
+    @JsonProperty
+    private String requestId;
+    @JsonProperty
+    private Status status;
+    @JsonProperty
+    private ResultSet result;
+
+    public void graphManager(GraphManager graphManager) {
+        this.result.graphManager(graphManager);
+    }
+
+    public String requestId() {
+        return this.requestId;
+    }
+
+    public Status status() {
+        return this.status;
+    }
+
+    public ResultSet result() {
+        return this.result;
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/gremlin/Result.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/gremlin/Result.java
new file mode 100644
index 0000000..2d5a13e
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/gremlin/Result.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.gremlin;
+
+import com.baidu.hugegraph.structure.graph.Edge;
+import com.baidu.hugegraph.structure.graph.Path;
+import com.baidu.hugegraph.structure.graph.Vertex;
+
+public class Result {
+
+    private Object object;
+
+    public Result(Object object) {
+        this.object = object;
+    }
+
+    public Object getObject() {
+        return this.object;
+    }
+
+    public String getString() {
+        return this.object.toString();
+    }
+
+    public int getInt() {
+        return Integer.parseInt(this.object.toString());
+    }
+
+    public byte getByte() {
+        return Byte.parseByte(this.object.toString());
+    }
+
+    public short getShort() {
+        return Short.parseShort(this.object.toString());
+    }
+
+    public long getLong() {
+        return Long.parseLong(this.object.toString());
+    }
+
+    public float getFloat() {
+        return Float.parseFloat(this.object.toString());
+    }
+
+    public double getDouble() {
+        return Double.parseDouble(this.object.toString());
+    }
+
+    public boolean getBoolean() {
+        return Boolean.parseBoolean(this.object.toString());
+    }
+
+    public boolean isNull() {
+        return null == this.object;
+    }
+
+    public Vertex getVertex() {
+        return (Vertex) this.object;
+    }
+
+    public Edge getEdge() {
+        return (Edge) this.object;
+    }
+
+    public Path getPath() {
+        return (Path) this.object;
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/gremlin/ResultSet.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/gremlin/ResultSet.java
new file mode 100644
index 0000000..3b93fa3
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/gremlin/ResultSet.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.gremlin;
+
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+
+import com.baidu.hugegraph.driver.GraphManager;
+import com.baidu.hugegraph.rest.SerializeException;
+import com.baidu.hugegraph.serializer.PathDeserializer;
+import com.baidu.hugegraph.structure.constant.GraphAttachable;
+import com.baidu.hugegraph.structure.graph.Edge;
+import com.baidu.hugegraph.structure.graph.Path;
+import com.baidu.hugegraph.structure.graph.Vertex;
+import com.baidu.hugegraph.util.E;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+
+public class ResultSet {
+
+    private static final ObjectMapper MAPPER = new ObjectMapper();
+
+    private GraphManager graphManager = null;
+
+    @JsonProperty
+    private List<Object> data;
+    @JsonProperty
+    private Map<String, ?> meta;
+
+    static {
+        SimpleModule module = new SimpleModule();
+        module.addDeserializer(Path.class, new PathDeserializer());
+        MAPPER.registerModule(module);
+    }
+
+    public void graphManager(GraphManager graphManager) {
+        this.graphManager = graphManager;
+    }
+
+    public List<Object> data() {
+        return this.data;
+    }
+
+    public int size() {
+        return this.data.size();
+    }
+
+    public Result get(int index) {
+        if (index >= this.data.size()) {
+            return null;
+        }
+
+        Object object = this.data().get(index);
+        if (object == null) {
+            return null;
+        }
+
+        Class<?> clazz = this.parseResultClass(object);
+        // Primitive type
+        if (clazz.equals(object.getClass())) {
+            return new Result(object);
+        }
+
+        try {
+            String rawValue = MAPPER.writeValueAsString(object);
+            object = MAPPER.readValue(rawValue, clazz);
+            if (object instanceof GraphAttachable) {
+                ((GraphAttachable) object).attachManager(graphManager);
+            }
+            return new Result(object);
+        } catch (Exception e) {
+            throw new SerializeException(
+                      "Failed to deserialize: %s", e, object);
+        }
+    }
+
+    /**
+     * TODO: Still need to constantly add and optimize
+     */
+    private Class<?> parseResultClass(Object object) {
+        if (object.getClass().equals(LinkedHashMap.class)) {
+            @SuppressWarnings("unchecked")
+            Map<String, Object> map = (Map<String, Object>) object;
+            String type = (String) map.get("type");
+            if (type != null) {
+                if ("vertex".equals(type)) {
+                    return Vertex.class;
+                } else if ("edge".equals(type)) {
+                    return Edge.class;
+                }
+            } else {
+                if (map.get("labels") != null) {
+                    return Path.class;
+                }
+            }
+        }
+
+        return object.getClass();
+    }
+
+    public Iterator<Result> iterator() {
+        E.checkState(this.data != null, "Invalid response from server");
+        E.checkState(this.graphManager != null, "Must hold a graph manager");
+
+        return new Iterator<Result>() {
+
+            private int index = 0;
+
+            @Override
+            public boolean hasNext() {
+                return this.index < ResultSet.this.data.size();
+            }
+
+            @Override
+            public Result next() {
+                if (!this.hasNext()) {
+                    throw new NoSuchElementException();
+                }
+                return get(this.index++);
+            }
+
+            @Override
+            public void remove() {
+                throw new UnsupportedOperationException();
+            }
+        };
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/schema/BuilderProxy.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/schema/BuilderProxy.java
new file mode 100644
index 0000000..d0460b0
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/schema/BuilderProxy.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.schema;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+
+import com.baidu.hugegraph.exception.InvalidOperationException;
+
+public class BuilderProxy<T> implements InvocationHandler {
+
+    private boolean finished;
+    private T builder;
+    private T proxy;
+
+    @SuppressWarnings("unchecked")
+    public BuilderProxy(T builder) {
+        this.finished = false;
+        this.builder = builder;
+        this.proxy = (T) Proxy.newProxyInstance(
+                     builder.getClass().getClassLoader(),
+                     builder.getClass().getInterfaces(),
+                     this);
+    }
+
+    public T proxy() {
+        return this.proxy;
+    }
+
+    @Override
+    public Object invoke(Object proxy, Method method, Object[] args)
+                         throws Throwable {
+        if (this.finished) {
+            throw new InvalidOperationException(
+                      "Can't access builder which is completed");
+        }
+        // The result may be equal this.builder, like method `asText`
+        Object result = method.invoke(this.builder, args);
+        if (result == this.builder) {
+            result = this.proxy;
+        } else {
+            this.finished = true;
+        }
+        return result;
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/schema/EdgeLabel.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/schema/EdgeLabel.java
new file mode 100644
index 0000000..7453784
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/schema/EdgeLabel.java
@@ -0,0 +1,341 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.schema;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import com.baidu.hugegraph.driver.SchemaManager;
+import com.baidu.hugegraph.structure.constant.Frequency;
+import com.baidu.hugegraph.structure.constant.HugeType;
+import com.baidu.hugegraph.util.CollectionUtil;
+import com.baidu.hugegraph.util.E;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class EdgeLabel extends SchemaLabel {
+
+    @JsonProperty("frequency")
+    private Frequency frequency;
+    @JsonProperty("source_label")
+    private String sourceLabel;
+    @JsonProperty("target_label")
+    private String targetLabel;
+    @JsonProperty("sort_keys")
+    private List<String> sortKeys;
+    @JsonProperty("ttl")
+    private long ttl;
+    @JsonProperty("ttl_start_time")
+    private String ttlStartTime;
+
+    @JsonCreator
+    public EdgeLabel(@JsonProperty("name") String name) {
+        super(name);
+        this.frequency = Frequency.DEFAULT;
+        this.sortKeys = new CopyOnWriteArrayList<>();
+        this.ttl = 0L;
+        this.ttlStartTime = null;
+    }
+
+    @Override
+    public String type() {
+        return HugeType.EDGE_LABEL.string();
+    }
+
+    public Frequency frequency() {
+        return this.frequency;
+    }
+
+    public String sourceLabel() {
+        return this.sourceLabel;
+    }
+
+    public String targetLabel() {
+        return this.targetLabel;
+    }
+
+    public boolean linkedVertexLabel(String vertexLabel) {
+        return this.sourceLabel.equals(vertexLabel) ||
+               this.targetLabel.equals(vertexLabel);
+    }
+
+    public List<String> sortKeys() {
+        return this.sortKeys;
+    }
+
+    public long ttl() {
+        return this.ttl;
+    }
+
+    public String ttlStartTime() {
+        return this.ttlStartTime;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("{name=%s, sourceLabel=%s, targetLabel=%s, " +
+                             "sortKeys=%s, indexLabels=%s, nullableKeys=%s, " +
+                             "properties=%s, ttl=%s, ttlStartTime=%s, " +
+                             "status=%s}",
+                             this.name, this.sourceLabel, this.targetLabel,
+                             this.sortKeys, this.indexLabels,
+                             this.nullableKeys, this.properties, this.ttl,
+                             this.ttlStartTime, this.status);
+    }
+
+    public EdgeLabelV53 switchV53() {
+        return new EdgeLabelV53(this);
+    }
+
+    public interface Builder extends SchemaBuilder<EdgeLabel> {
+
+        Builder properties(String... properties);
+
+        Builder sortKeys(String... keys);
+
+        Builder nullableKeys(String... keys);
+
+        Builder link(String sourceLabel, String targetLabel);
+
+        Builder sourceLabel(String label);
+
+        Builder targetLabel(String label);
+
+        Builder frequency(Frequency frequency);
+
+        Builder singleTime();
+
+        Builder multiTimes();
+
+        Builder ttl(long ttl);
+
+        Builder ttlStartTime(String ttlStartTime);
+
+        Builder enableLabelIndex(boolean enable);
+
+        Builder userdata(String key, Object val);
+
+        Builder ifNotExist();
+    }
+
+    public static class BuilderImpl implements Builder {
+
+        private EdgeLabel edgeLabel;
+        private SchemaManager manager;
+
+        public BuilderImpl(String name, SchemaManager manager) {
+            this.edgeLabel = new EdgeLabel(name);
+            this.manager = manager;
+        }
+
+        @Override
+        public EdgeLabel build() {
+            return this.edgeLabel;
+        }
+
+        @Override
+        public EdgeLabel create() {
+            return this.manager.addEdgeLabel(this.edgeLabel);
+        }
+
+        @Override
+        public EdgeLabel append() {
+            return this.manager.appendEdgeLabel(this.edgeLabel);
+        }
+
+        @Override
+        public EdgeLabel eliminate() {
+            return this.manager.eliminateEdgeLabel(this.edgeLabel);
+        }
+
+        @Override
+        public void remove() {
+            this.manager.removeEdgeLabel(this.edgeLabel.name);
+        }
+
+        @Override
+        public Builder properties(String... properties) {
+            this.edgeLabel.properties.addAll(Arrays.asList(properties));
+            return this;
+        }
+
+        @Override
+        public Builder sortKeys(String... keys) {
+            E.checkArgument(this.edgeLabel.sortKeys.isEmpty(),
+                            "Not allowed to assign sort keys multi times");
+            List<String> sortKeys = Arrays.asList(keys);
+            E.checkArgument(CollectionUtil.allUnique(sortKeys),
+                            "Invalid sort keys %s, which contains some " +
+                            "duplicate properties", sortKeys);
+            this.edgeLabel.sortKeys.addAll(sortKeys);
+            return this;
+        }
+
+        @Override
+        public Builder nullableKeys(String... keys) {
+            this.edgeLabel.nullableKeys.addAll(Arrays.asList(keys));
+            return this;
+        }
+
+        @Override
+        public Builder link(String sourceLabel, String targetLabel) {
+            this.edgeLabel.sourceLabel = sourceLabel;
+            this.edgeLabel.targetLabel = targetLabel;
+            return this;
+        }
+
+        @Override
+        public Builder sourceLabel(String label) {
+            this.edgeLabel.sourceLabel = label;
+            return this;
+        }
+
+        @Override
+        public Builder targetLabel(String label) {
+            this.edgeLabel.targetLabel = label;
+            return this;
+        }
+
+        @Override
+        public Builder frequency(Frequency frequency) {
+            this.checkFrequency();
+            this.edgeLabel.frequency = frequency;
+            return this;
+        }
+
+        @Override
+        public Builder singleTime() {
+            this.checkFrequency();
+            this.edgeLabel.frequency = Frequency.SINGLE;
+            return this;
+        }
+
+        @Override
+        public Builder multiTimes() {
+            this.checkFrequency();
+            this.edgeLabel.frequency = Frequency.MULTIPLE;
+            return this;
+        }
+
+
+        @Override
+        public Builder ttl(long ttl) {
+            E.checkArgument(ttl >= 0L, "The ttl must >= 0, but got: %s", ttl);
+            this.edgeLabel.ttl = ttl;
+            return this;
+        }
+
+        @Override
+        public Builder ttlStartTime(String ttlStartTime) {
+            this.edgeLabel.ttlStartTime = ttlStartTime;
+            return this;
+        }
+
+        @Override
+        public Builder enableLabelIndex(boolean enable) {
+            this.edgeLabel.enableLabelIndex = enable;
+            return this;
+        }
+
+        @Override
+        public Builder userdata(String key, Object val) {
+            E.checkArgumentNotNull(key, "The user data key can't be null");
+            E.checkArgumentNotNull(val, "The user data value can't be null");
+            this.edgeLabel.userdata.put(key, val);
+            return this;
+        }
+
+        @Override
+        public Builder ifNotExist() {
+            this.edgeLabel.checkExist = false;
+            return this;
+        }
+
+        private void checkFrequency() {
+            E.checkArgument(this.edgeLabel.frequency == Frequency.DEFAULT,
+                            "Not allowed to change frequency for " +
+                            "edge label '%s'", this.edgeLabel.name);
+        }
+    }
+
+    public static class EdgeLabelV53 extends SchemaLabel {
+
+        @JsonProperty("frequency")
+        private Frequency frequency;
+        @JsonProperty("source_label")
+        private String sourceLabel;
+        @JsonProperty("target_label")
+        private String targetLabel;
+        @JsonProperty("sort_keys")
+        private List<String> sortKeys;
+
+        @JsonCreator
+        public EdgeLabelV53(@JsonProperty("name") String name) {
+            super(name);
+            this.frequency = Frequency.DEFAULT;
+            this.sortKeys = new CopyOnWriteArrayList<>();
+        }
+
+        private EdgeLabelV53(EdgeLabel edgeLabel) {
+            super(edgeLabel.name);
+            this.frequency = edgeLabel.frequency;
+            this.sortKeys = edgeLabel.sortKeys;
+            this.sourceLabel = edgeLabel.sourceLabel;
+            this.targetLabel = edgeLabel.targetLabel;
+            this.id = edgeLabel.id();
+            this.properties = edgeLabel.properties();
+            this.userdata = edgeLabel.userdata();
+            this.checkExist = edgeLabel.checkExist();
+            this.nullableKeys = edgeLabel.nullableKeys;
+            this.enableLabelIndex = edgeLabel.enableLabelIndex;
+        }
+
+        public Frequency frequency() {
+            return this.frequency;
+        }
+
+        public List<String> sortKeys() {
+            return this.sortKeys;
+        }
+
+        public String sourceLabel() {
+            return this.sourceLabel;
+        }
+
+        public String targetLabel() {
+            return this.targetLabel;
+        }
+
+        @Override
+        public String toString() {
+            return String.format("{name=%s, sourceLabel=%s, targetLabel=%s, " +
+                                 "sortKeys=%s, nullableKeys=%s, properties=%s}",
+                                 this.name, this.sourceLabel, this.targetLabel,
+                                 this.sortKeys, this.nullableKeys,
+                                 this.properties);
+        }
+
+        @Override
+        public String type() {
+            return HugeType.EDGE_LABEL.string();
+        }
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/schema/IndexLabel.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/schema/IndexLabel.java
new file mode 100644
index 0000000..b995140
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/schema/IndexLabel.java
@@ -0,0 +1,310 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.schema;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import com.baidu.hugegraph.driver.SchemaManager;
+import com.baidu.hugegraph.exception.NotSupportException;
+import com.baidu.hugegraph.structure.SchemaElement;
+import com.baidu.hugegraph.structure.constant.HugeType;
+import com.baidu.hugegraph.structure.constant.IndexType;
+import com.baidu.hugegraph.util.CollectionUtil;
+import com.baidu.hugegraph.util.E;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+@JsonIgnoreProperties({"properties"})
+public class IndexLabel extends SchemaElement {
+
+    @JsonProperty("base_type")
+    protected HugeType baseType;
+    @JsonProperty("base_value")
+    protected String baseValue;
+    @JsonProperty("index_type")
+    protected IndexType indexType;
+    @JsonProperty("fields")
+    protected List<String> fields;
+    @JsonProperty("rebuild")
+    protected boolean rebuild = true;
+
+    @JsonCreator
+    public IndexLabel(@JsonProperty("name") String name) {
+        super(name);
+        this.indexType = null;
+        this.fields = new CopyOnWriteArrayList<>();
+    }
+
+    @Override
+    public String type() {
+        return HugeType.INDEX_LABEL.string();
+    }
+
+    public HugeType baseType() {
+        return this.baseType;
+    }
+
+    public String baseValue() {
+        return this.baseValue;
+    }
+
+    public IndexType indexType() {
+        return this.indexType;
+    }
+
+    public List<String> indexFields() {
+        return this.fields;
+    }
+
+    public boolean rebuild() {
+        return this.rebuild;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("{name=%s, baseType=%s, baseValue=%s, " +
+                             "indexType=%s, fields=%s, status=%s}",
+                             this.name, this.baseType, this.baseValue,
+                             this.indexType, this.fields, this.status);
+    }
+
+    public IndexLabelV49 switchV49() {
+        return new IndexLabelV49(this);
+    }
+
+    public IndexLabelV56 switchV56() {
+        return new IndexLabelV56(this);
+    }
+
+    public interface Builder extends SchemaBuilder<IndexLabel> {
+
+        Builder on(boolean isVertex, String baseValue);
+
+        Builder onV(String baseValue);
+
+        Builder onE(String baseValue);
+
+        Builder by(String... fields);
+
+        Builder indexType(IndexType indexType);
+
+        Builder secondary();
+
+        Builder range();
+
+        Builder search();
+
+        Builder shard();
+
+        Builder unique();
+
+        Builder userdata(String key, Object val);
+
+        Builder ifNotExist();
+
+        Builder rebuild(boolean rebuild);
+    }
+
+    public static class BuilderImpl implements Builder {
+
+        private IndexLabel indexLabel;
+        private SchemaManager manager;
+
+        public BuilderImpl(String name, SchemaManager manager) {
+            this.indexLabel = new IndexLabel(name);
+            this.manager = manager;
+        }
+
+        @Override
+        public IndexLabel build() {
+            return this.indexLabel;
+        }
+
+        @Override
+        public IndexLabel create() {
+            return this.manager.addIndexLabel(this.indexLabel);
+        }
+
+        @Override
+        public IndexLabel append() {
+            return this.manager.appendIndexLabel(this.indexLabel);
+        }
+
+        @Override
+        public IndexLabel eliminate() {
+            return this.manager.eliminateIndexLabel(this.indexLabel);
+        }
+
+        @Override
+        public void remove() {
+            this.manager.removeIndexLabel(this.indexLabel.name);
+        }
+
+        @Override
+        public Builder on(boolean isVertex, String baseValue) {
+            return isVertex ? this.onV(baseValue) : this.onE(baseValue);
+        }
+
+        @Override
+        public Builder onV(String baseValue) {
+            this.indexLabel.baseType = HugeType.VERTEX_LABEL;
+            this.indexLabel.baseValue = baseValue;
+            return this;
+        }
+
+        @Override
+        public Builder onE(String baseValue) {
+            this.indexLabel.baseType = HugeType.EDGE_LABEL;
+            this.indexLabel.baseValue = baseValue;
+            return this;
+        }
+
+        @Override
+        public Builder by(String... fields) {
+            E.checkArgument(this.indexLabel.fields.isEmpty(),
+                            "Not allowed to assign index fields multi times");
+            List<String> indexFields = Arrays.asList(fields);
+            E.checkArgument(CollectionUtil.allUnique(indexFields),
+                            "Invalid index fields %s, which contains some " +
+                            "duplicate properties", indexFields);
+            this.indexLabel.fields.addAll(indexFields);
+            return this;
+        }
+
+        @Override
+        public Builder indexType(IndexType indexType) {
+            this.indexLabel.indexType = indexType;
+            return this;
+        }
+
+        @Override
+        public Builder secondary() {
+            this.indexLabel.indexType = IndexType.SECONDARY;
+            return this;
+        }
+
+        @Override
+        public Builder range() {
+            this.indexLabel.indexType = IndexType.RANGE;
+            return this;
+        }
+
+        @Override
+        public Builder search() {
+            this.indexLabel.indexType = IndexType.SEARCH;
+            return this;
+        }
+
+        @Override
+        public Builder shard() {
+            this.indexLabel.indexType = IndexType.SHARD;
+            return this;
+        }
+
+        @Override
+        public Builder unique() {
+            this.indexLabel.indexType = IndexType.UNIQUE;
+            return this;
+        }
+
+        @Override
+        public Builder userdata(String key, Object val) {
+            E.checkArgumentNotNull(key, "The user data key can't be null");
+            E.checkArgumentNotNull(val, "The user data value can't be null");
+            this.indexLabel.userdata.put(key, val);
+            return this;
+        }
+
+        @Override
+        public Builder ifNotExist() {
+            this.indexLabel.checkExist = false;
+            return this;
+        }
+
+        @Override
+        public Builder rebuild(boolean rebuild) {
+            this.indexLabel.rebuild = rebuild;
+            return this;
+        }
+    }
+
+    public static class IndexLabelWithTask {
+
+        @JsonProperty("index_label")
+        private IndexLabel indexLabel;
+
+        @JsonProperty("task_id")
+        private long taskId;
+
+        public IndexLabel indexLabel() {
+            return this.indexLabel;
+        }
+
+        public long taskId() {
+            return this.taskId;
+        }
+    }
+
+    @JsonIgnoreProperties({"properties", "rebuild"})
+    public static class IndexLabelV56 extends IndexLabel {
+
+        public IndexLabelV56(IndexLabel indexLabel) {
+            super(indexLabel.name);
+            E.checkArgument(indexLabel.rebuild,
+                            "The rebuild of indexlabel must be true");
+            this.id = indexLabel.id();
+            this.baseType = indexLabel.baseType;
+            this.baseValue = indexLabel.baseValue;
+            this.indexType = indexLabel.indexType;
+            this.fields = indexLabel.fields;
+        }
+
+        @Override
+        public boolean rebuild() {
+            throw new NotSupportException("rebuild for index label");
+        }
+    }
+
+    @JsonIgnoreProperties({"properties", "user_data", "rebuild"})
+    public static class IndexLabelV49 extends IndexLabel {
+
+        public IndexLabelV49(IndexLabel indexLabel) {
+            super(indexLabel.name);
+            E.checkArgument(indexLabel.userdata.isEmpty(),
+                            "The userdata of indexlabel must be empty");
+            E.checkArgument(indexLabel.rebuild,
+                            "The rebuild of indexlabel must be true");
+            this.id = indexLabel.id();
+            this.baseType = indexLabel.baseType;
+            this.baseValue = indexLabel.baseValue;
+            this.indexType = indexLabel.indexType;
+            this.fields = indexLabel.fields;
+        }
+
+        @Override
+        public Map<String, Object> userdata() {
+            throw new NotSupportException("user data for index label");
+        }
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/schema/PropertyKey.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/schema/PropertyKey.java
new file mode 100644
index 0000000..1be7d39
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/schema/PropertyKey.java
@@ -0,0 +1,416 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.schema;
+
+import com.baidu.hugegraph.driver.SchemaManager;
+import com.baidu.hugegraph.structure.SchemaElement;
+import com.baidu.hugegraph.structure.constant.AggregateType;
+import com.baidu.hugegraph.structure.constant.Cardinality;
+import com.baidu.hugegraph.structure.constant.DataType;
+import com.baidu.hugegraph.structure.constant.HugeType;
+import com.baidu.hugegraph.structure.constant.WriteType;
+import com.baidu.hugegraph.util.E;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class PropertyKey extends SchemaElement {
+
+    @JsonProperty("data_type")
+    private DataType dataType;
+    @JsonProperty("cardinality")
+    private Cardinality cardinality;
+    @JsonProperty("aggregate_type")
+    private AggregateType aggregateType;
+    @JsonProperty("write_type")
+    private WriteType writeType;
+
+    @JsonCreator
+    public PropertyKey(@JsonProperty("name") String name) {
+        super(name);
+        this.dataType = DataType.TEXT;
+        this.cardinality = Cardinality.SINGLE;
+        this.aggregateType = AggregateType.NONE;
+        this.writeType = WriteType.OLTP;
+    }
+
+    @Override
+    public String type() {
+        return HugeType.PROPERTY_KEY.string();
+    }
+
+    public DataType dataType() {
+        return this.dataType;
+    }
+
+    public Cardinality cardinality() {
+        return this.cardinality;
+    }
+
+    public AggregateType aggregateType() {
+        return this.aggregateType;
+    }
+
+    public WriteType writeType() {
+        return this.writeType;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("{name=%s, cardinality=%s, dataType=%s, " +
+                             "aggregateType=%s, properties=%s, " +
+                             "writeType=%s}",
+                             this.name, this.cardinality, this.dataType,
+                             this.aggregateType, this.properties,
+                             this.writeType);
+    }
+
+    public PropertyKeyV46 switchV46() {
+        return new PropertyKeyV46(this);
+    }
+
+    public PropertyKeyV58 switchV58() {
+        return new PropertyKeyV58(this);
+    }
+
+    public interface Builder extends SchemaBuilder<PropertyKey> {
+
+        Builder dataType(DataType dataType);
+
+        Builder asText();
+
+        Builder asInt();
+
+        Builder asDate();
+
+        Builder asUUID();
+
+        Builder asBoolean();
+
+        Builder asByte();
+
+        Builder asBlob();
+
+        Builder asDouble();
+
+        Builder asFloat();
+
+        Builder asLong();
+
+        Builder cardinality(Cardinality cardinality);
+
+        Builder valueSingle();
+
+        Builder valueList();
+
+        Builder valueSet();
+
+        Builder aggregateType(AggregateType aggregateType);
+
+        Builder writeType(WriteType writeType);
+
+        Builder calcSum();
+
+        Builder calcMax();
+
+        Builder calcMin();
+
+        Builder calcOld();
+
+        Builder userdata(String key, Object val);
+
+        Builder ifNotExist();
+    }
+
+    public static class BuilderImpl implements Builder {
+
+        private PropertyKey propertyKey;
+        private SchemaManager manager;
+
+        public BuilderImpl(String name, SchemaManager manager) {
+            this.propertyKey = new PropertyKey(name);
+            this.manager = manager;
+        }
+
+        @Override
+        public PropertyKey build() {
+            return this.propertyKey;
+        }
+
+        @Override
+        public PropertyKey create() {
+            return this.manager.addPropertyKey(this.propertyKey);
+        }
+
+        @Override
+        public PropertyKey append() {
+            return this.manager.appendPropertyKey(this.propertyKey);
+        }
+
+        @Override
+        public PropertyKey eliminate() {
+            return this.manager.eliminatePropertyKey(this.propertyKey);
+        }
+
+        @Override
+        public void remove() {
+            this.manager.removePropertyKey(this.propertyKey.name);
+        }
+
+        @Override
+        public Builder dataType(DataType dataType) {
+            this.propertyKey.dataType = dataType;
+            return this;
+        }
+
+        @Override
+        public Builder asText() {
+            this.propertyKey.dataType = DataType.TEXT;
+            return this;
+        }
+
+        @Override
+        public Builder asInt() {
+            this.propertyKey.dataType = DataType.INT;
+            return this;
+        }
+
+        @Override
+        public Builder asDate() {
+            this.propertyKey.dataType = DataType.DATE;
+            return this;
+        }
+
+        @Override
+        public Builder asUUID() {
+            this.propertyKey.dataType = DataType.UUID;
+            return this;
+        }
+
+        @Override
+        public Builder asBoolean() {
+            this.propertyKey.dataType = DataType.BOOLEAN;
+            return this;
+        }
+
+        @Override
+        public Builder asByte() {
+            this.propertyKey.dataType = DataType.BYTE;
+            return this;
+        }
+
+        @Override
+        public Builder asBlob() {
+            this.propertyKey.dataType = DataType.BLOB;
+            return this;
+        }
+
+        @Override
+        public Builder asDouble() {
+            this.propertyKey.dataType = DataType.DOUBLE;
+            return this;
+        }
+
+        @Override
+        public Builder asFloat() {
+            this.propertyKey.dataType = DataType.FLOAT;
+            return this;
+        }
+
+        @Override
+        public Builder asLong() {
+            this.propertyKey.dataType = DataType.LONG;
+            return this;
+        }
+
+        @Override
+        public Builder cardinality(Cardinality cardinality) {
+            this.propertyKey.cardinality = cardinality;
+            return this;
+        }
+
+        @Override
+        public Builder valueSingle() {
+            this.propertyKey.cardinality = Cardinality.SINGLE;
+            return this;
+        }
+
+        @Override
+        public Builder valueList() {
+            this.propertyKey.cardinality = Cardinality.LIST;
+            return this;
+        }
+
+        @Override
+        public Builder valueSet() {
+            this.propertyKey.cardinality = Cardinality.SET;
+            return this;
+        }
+
+        @Override
+        public Builder aggregateType(AggregateType aggregateType) {
+            this.propertyKey.aggregateType = aggregateType;
+            return this;
+        }
+
+        @Override
+        public Builder writeType(WriteType writeType) {
+            this.propertyKey.writeType = writeType;
+            return this;
+        }
+
+        @Override
+        public Builder calcSum() {
+            this.propertyKey.aggregateType = AggregateType.SUM;
+            return this;
+        }
+
+        @Override
+        public Builder calcMax() {
+            this.propertyKey.aggregateType = AggregateType.MAX;
+            return this;
+        }
+
+        @Override
+        public Builder calcMin() {
+            this.propertyKey.aggregateType = AggregateType.MIN;
+            return this;
+        }
+
+        @Override
+        public Builder calcOld() {
+            this.propertyKey.aggregateType = AggregateType.OLD;
+            return this;
+        }
+
+        @Override
+        public Builder userdata(String key, Object val) {
+            E.checkArgumentNotNull(key, "The user data key can't be null");
+            E.checkArgumentNotNull(val, "The user data value can't be null");
+            this.propertyKey.userdata.put(key, val);
+            return this;
+        }
+
+        @Override
+        public Builder ifNotExist() {
+            this.propertyKey.checkExist = false;
+            return this;
+        }
+    }
+
+    public static class PropertyKeyWithTask {
+
+        @JsonProperty("property_key")
+        private PropertyKey propertyKey;
+
+        @JsonProperty("task_id")
+        private long taskId;
+
+        @JsonCreator
+        public PropertyKeyWithTask(@JsonProperty("property_key")
+                                   PropertyKey propertyKey,
+                                   @JsonProperty("task_id")
+                                   long taskId) {
+            this.propertyKey = propertyKey;
+            this.taskId = taskId;
+        }
+
+        public PropertyKey propertyKey() {
+            return this.propertyKey;
+        }
+
+        public long taskId() {
+            return this.taskId;
+        }
+    }
+
+    public static class PropertyKeyV46 extends SchemaElement {
+
+        @JsonProperty("data_type")
+        private DataType dataType;
+        @JsonProperty("cardinality")
+        private Cardinality cardinality;
+
+        @JsonCreator
+        public PropertyKeyV46(@JsonProperty("name") String name) {
+            super(name);
+            this.dataType = DataType.TEXT;
+            this.cardinality = Cardinality.SINGLE;
+        }
+
+        private PropertyKeyV46(PropertyKey propertyKey) {
+            super(propertyKey.name);
+            this.dataType = propertyKey.dataType;
+            this.cardinality = propertyKey.cardinality;
+            this.id = propertyKey.id();
+            this.properties = propertyKey.properties();
+            this.userdata = propertyKey.userdata();
+            this.checkExist = propertyKey.checkExist();
+        }
+
+        public DataType dataType() {
+            return this.dataType;
+        }
+
+        public Cardinality cardinality() {
+            return this.cardinality;
+        }
+
+        @Override
+        public String toString() {
+            return String.format("{name=%s, cardinality=%s, dataType=%s, " +
+                                 "properties=%s}", this.name, this.cardinality,
+                                 this.dataType, this.properties);
+        }
+
+        @Override
+        public String type() {
+            return HugeType.PROPERTY_KEY.string();
+        }
+    }
+
+    public static class PropertyKeyV58 extends PropertyKeyV46 {
+
+        @JsonProperty("aggregate_type")
+        private AggregateType aggregateType;
+
+        @JsonCreator
+        public PropertyKeyV58(@JsonProperty("name") String name) {
+            super(name);
+            this.aggregateType = AggregateType.NONE;
+        }
+
+        private PropertyKeyV58(PropertyKey propertyKey) {
+            super(propertyKey);
+            this.aggregateType = propertyKey.aggregateType;
+        }
+
+        public AggregateType aggregateType() {
+            return this.aggregateType;
+        }
+
+        @Override
+        public String toString() {
+            return String.format("{name=%s, cardinality=%s, dataType=%s, " +
+                                 "aggregateType=%s, properties=%s}", this.name,
+                                 this.cardinality(), this.dataType(),
+                                 this.aggregateType, this.properties);
+        }
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/schema/SchemaBuilder.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/schema/SchemaBuilder.java
new file mode 100644
index 0000000..b728275
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/schema/SchemaBuilder.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.schema;
+
+import com.baidu.hugegraph.structure.SchemaElement;
+
+public interface SchemaBuilder<T extends SchemaElement> {
+
+    T build();
+
+    T create();
+
+    T append();
+
+    T eliminate();
+
+    void remove();
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/schema/SchemaLabel.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/schema/SchemaLabel.java
new file mode 100644
index 0000000..89dd4c3
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/schema/SchemaLabel.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.schema;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ConcurrentSkipListSet;
+
+import com.baidu.hugegraph.structure.SchemaElement;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.collect.ImmutableList;
+
+public abstract class SchemaLabel extends SchemaElement {
+
+    @JsonProperty("nullable_keys")
+    protected Set<String> nullableKeys;
+    @JsonProperty("enable_label_index")
+    protected Boolean enableLabelIndex;
+    @JsonProperty("index_labels")
+    protected List<String> indexLabels;
+
+    public SchemaLabel(String name) {
+        super(name);
+        this.nullableKeys = new ConcurrentSkipListSet<>();
+        this.indexLabels = null;
+        this.enableLabelIndex = null;
+    }
+
+    public Set<String> nullableKeys() {
+        return this.nullableKeys;
+    }
+
+    public List<String> indexLabels() {
+        if (this.indexLabels == null) {
+            return ImmutableList.of();
+        }
+        return Collections.unmodifiableList(this.indexLabels);
+    }
+
+    public boolean enableLabelIndex() {
+        return this.enableLabelIndex;
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/schema/VertexLabel.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/schema/VertexLabel.java
new file mode 100644
index 0000000..c859f92
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/schema/VertexLabel.java
@@ -0,0 +1,309 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.schema;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import com.baidu.hugegraph.driver.SchemaManager;
+import com.baidu.hugegraph.structure.constant.HugeType;
+import com.baidu.hugegraph.structure.constant.IdStrategy;
+import com.baidu.hugegraph.util.CollectionUtil;
+import com.baidu.hugegraph.util.E;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class VertexLabel extends SchemaLabel {
+
+    @JsonProperty("id_strategy")
+    private IdStrategy idStrategy;
+    @JsonProperty("primary_keys")
+    private List<String> primaryKeys;
+    @JsonProperty("ttl")
+    private long ttl;
+    @JsonProperty("ttl_start_time")
+    private String ttlStartTime;
+
+    @JsonCreator
+    public VertexLabel(@JsonProperty("name") String name) {
+        super(name);
+        this.idStrategy = IdStrategy.DEFAULT;
+        this.primaryKeys = new CopyOnWriteArrayList<>();
+        this.ttl = 0L;
+        this.ttlStartTime = null;
+    }
+
+    @Override
+    public String type() {
+        return HugeType.VERTEX_LABEL.string();
+    }
+
+    public IdStrategy idStrategy() {
+        return this.idStrategy;
+    }
+
+    public List<String> primaryKeys() {
+        return this.primaryKeys;
+    }
+
+    public long ttl() {
+        return this.ttl;
+    }
+
+    public String ttlStartTime() {
+        return this.ttlStartTime;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("{name=%s, idStrategy=%s, primaryKeys=%s, " +
+                             "indexLabels=%s, nullableKeys=%s, properties=%s," +
+                             " ttl=%s, ttlStartTime=%s, status=%s}",
+                             this.name, this.idStrategy, this.primaryKeys,
+                             this.indexLabels, this.nullableKeys,
+                             this.properties, this.ttl, this.ttlStartTime,
+                             this.status);
+    }
+
+    public VertexLabelV53 switchV53() {
+        return new VertexLabelV53(this);
+    }
+
+    public interface Builder extends SchemaBuilder<VertexLabel> {
+
+        Builder idStrategy(IdStrategy idStrategy);
+
+        Builder useAutomaticId();
+
+        Builder usePrimaryKeyId();
+
+        Builder useCustomizeStringId();
+
+        Builder useCustomizeNumberId();
+
+        Builder useCustomizeUuidId();
+
+        Builder properties(String... properties);
+
+        Builder primaryKeys(String... keys);
+
+        Builder nullableKeys(String... keys);
+
+        Builder ttl(long ttl);
+
+        Builder ttlStartTime(String ttlStartTime);
+
+        Builder enableLabelIndex(boolean enable);
+
+        Builder userdata(String key, Object val);
+
+        Builder ifNotExist();
+    }
+
+    public static class BuilderImpl implements Builder {
+
+        private VertexLabel vertexLabel;
+        private SchemaManager manager;
+
+        public BuilderImpl(String name, SchemaManager manager) {
+            this.vertexLabel = new VertexLabel(name);
+            this.manager = manager;
+        }
+
+        @Override
+        public VertexLabel build() {
+            return this.vertexLabel;
+        }
+
+        @Override
+        public VertexLabel create() {
+            return this.manager.addVertexLabel(this.vertexLabel);
+        }
+
+        @Override
+        public VertexLabel append() {
+            return this.manager.appendVertexLabel(this.vertexLabel);
+        }
+
+        @Override
+        public VertexLabel eliminate() {
+            return this.manager.eliminateVertexLabel(this.vertexLabel);
+        }
+
+        @Override
+        public void remove() {
+            this.manager.removeVertexLabel(this.vertexLabel.name);
+        }
+
+        @Override
+        public Builder idStrategy(IdStrategy idStrategy) {
+            this.checkIdStrategy();
+            this.vertexLabel.idStrategy = idStrategy;
+            return this;
+        }
+
+        @Override
+        public Builder useAutomaticId() {
+            this.checkIdStrategy();
+            this.vertexLabel.idStrategy = IdStrategy.AUTOMATIC;
+            return this;
+        }
+
+        @Override
+        public Builder usePrimaryKeyId() {
+            this.checkIdStrategy();
+            this.vertexLabel.idStrategy = IdStrategy.PRIMARY_KEY;
+            return this;
+        }
+
+        @Override
+        public Builder useCustomizeStringId() {
+            this.checkIdStrategy();
+            this.vertexLabel.idStrategy = IdStrategy.CUSTOMIZE_STRING;
+            return this;
+        }
+
+        @Override
+        public Builder useCustomizeNumberId() {
+            this.checkIdStrategy();
+            this.vertexLabel.idStrategy = IdStrategy.CUSTOMIZE_NUMBER;
+            return this;
+        }
+
+        @Override
+        public Builder useCustomizeUuidId() {
+            this.checkIdStrategy();
+            this.vertexLabel.idStrategy = IdStrategy.CUSTOMIZE_UUID;
+            return this;
+        }
+
+        @Override
+        public Builder properties(String... properties) {
+            this.vertexLabel.properties.addAll(Arrays.asList(properties));
+            return this;
+        }
+
+        @Override
+        public Builder primaryKeys(String... keys) {
+            E.checkArgument(this.vertexLabel.primaryKeys.isEmpty(),
+                            "Not allowed to assign primary keys multi times");
+            List<String> primaryKeys = Arrays.asList(keys);
+            E.checkArgument(CollectionUtil.allUnique(primaryKeys),
+                            "Invalid primary keys %s, which contains some " +
+                            "duplicate properties", primaryKeys);
+            this.vertexLabel.primaryKeys.addAll(primaryKeys);
+            return this;
+        }
+
+        @Override
+        public Builder nullableKeys(String... keys) {
+            this.vertexLabel.nullableKeys.addAll(Arrays.asList(keys));
+            return this;
+        }
+
+        @Override
+        public Builder ttl(long ttl) {
+            E.checkArgument(ttl >= 0L, "The ttl must >= 0, but got: %s", ttl);
+            this.vertexLabel.ttl = ttl;
+            return this;
+        }
+
+        @Override
+        public Builder ttlStartTime(String ttlStartTime) {
+            this.vertexLabel.ttlStartTime = ttlStartTime;
+            return this;
+        }
+
+        @Override
+        public Builder enableLabelIndex(boolean enable) {
+            this.vertexLabel.enableLabelIndex = enable;
+            return this;
+        }
+
+        @Override
+        public Builder userdata(String key, Object val) {
+            E.checkArgumentNotNull(key, "The user data key can't be null");
+            E.checkArgumentNotNull(val, "The user data value can't be null");
+            this.vertexLabel.userdata.put(key, val);
+            return this;
+        }
+
+        @Override
+        public Builder ifNotExist() {
+            this.vertexLabel.checkExist = false;
+            return this;
+        }
+
+        private void checkIdStrategy() {
+            E.checkArgument(this.vertexLabel.idStrategy == IdStrategy.DEFAULT,
+                            "Not allowed to change id strategy for " +
+                            "vertex label '%s'", this.vertexLabel.name);
+        }
+    }
+
+    public static class VertexLabelV53 extends SchemaLabel {
+
+        @JsonProperty("id_strategy")
+        private IdStrategy idStrategy;
+        @JsonProperty("primary_keys")
+        private List<String> primaryKeys;
+
+        @JsonCreator
+        public VertexLabelV53(@JsonProperty("name") String name) {
+            super(name);
+            this.idStrategy = IdStrategy.DEFAULT;
+            this.primaryKeys = new CopyOnWriteArrayList<>();
+        }
+
+        private VertexLabelV53(VertexLabel vertexLabel) {
+            super(vertexLabel.name);
+            this.idStrategy = vertexLabel.idStrategy;
+            this.primaryKeys = vertexLabel.primaryKeys;
+            this.id = vertexLabel.id();
+            this.properties = vertexLabel.properties();
+            this.userdata = vertexLabel.userdata();
+            this.checkExist = vertexLabel.checkExist();
+            this.nullableKeys = vertexLabel.nullableKeys;
+            this.enableLabelIndex = vertexLabel.enableLabelIndex;
+        }
+
+        public IdStrategy idStrategy() {
+            return this.idStrategy;
+        }
+
+        public List<String> primaryKeys() {
+            return this.primaryKeys;
+        }
+
+        @Override
+        public String toString() {
+            return String.format("{name=%s, idStrategy=%s, primaryKeys=%s, " +
+                                 "nullableKeys=%s, properties=%s}",
+                                 this.name, this.idStrategy, this.primaryKeys,
+                                 this.nullableKeys, this.properties);
+        }
+
+        @Override
+        public String type() {
+            return HugeType.VERTEX_LABEL.string();
+        }
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/CountRequest.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/CountRequest.java
new file mode 100644
index 0000000..3d20aab
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/CountRequest.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.traverser;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.baidu.hugegraph.api.API;
+import com.baidu.hugegraph.structure.constant.Traverser;
+import com.baidu.hugegraph.util.E;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class CountRequest {
+
+    @JsonProperty("source")
+    private Object source;
+    @JsonProperty("steps")
+    private List<EdgeStep> steps;
+    @JsonProperty("contains_traversed")
+    public boolean containsTraversed;
+    @JsonProperty("dedup_size")
+    public long dedupSize;
+
+    private CountRequest() {
+        this.source = null;
+        this.steps = new ArrayList<>();
+        this.containsTraversed = false;
+        this.dedupSize = Traverser.DEFAULT_DEDUP_SIZE;
+    }
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    @Override
+    public String toString() {
+        return String.format("CountRequest{source=%s,steps=%s," +
+                             "containsTraversed=%s,dedupSize=%s",
+                             this.source, this.steps, this.containsTraversed,
+                             this.dedupSize);
+    }
+
+    public static class Builder {
+
+        private CountRequest request;
+        private List<EdgeStep.Builder> stepBuilders;
+
+        private Builder() {
+            this.request = new CountRequest();
+            this.stepBuilders = new ArrayList<>();
+        }
+
+        public Builder source(Object source) {
+            E.checkArgumentNotNull(source, "The source can't be null");
+            this.request.source = source;
+            return this;
+        }
+
+        public EdgeStep.Builder steps() {
+            EdgeStep.Builder builder = EdgeStep.builder();
+            this.stepBuilders.add(builder);
+            return builder;
+        }
+
+        public Builder containsTraversed(boolean containsTraversed) {
+            this.request.containsTraversed = containsTraversed;
+            return this;
+        }
+
+        public Builder dedupSize(long dedupSize) {
+            checkDedupSize(dedupSize);
+            this.request.dedupSize = dedupSize;
+            return this;
+        }
+
+        public CountRequest build() {
+            E.checkArgumentNotNull(this.request.source,
+                                   "The source can't be null");
+            for (EdgeStep.Builder builder : this.stepBuilders) {
+                this.request.steps.add(builder.build());
+            }
+            E.checkArgument(this.request.steps != null &&
+                            !this.request.steps.isEmpty(),
+                            "The steps can't be null or empty");
+            checkDedupSize(this.request.dedupSize);
+            return this.request;
+        }
+    }
+
+    private static void checkDedupSize(long dedupSize) {
+        E.checkArgument(dedupSize >= 0L || dedupSize == API.NO_LIMIT,
+                        "The dedup size must be >= 0 or == %s, but got: %s",
+                        API.NO_LIMIT, dedupSize);
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/CrosspointsRequest.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/CrosspointsRequest.java
new file mode 100644
index 0000000..6e222d4
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/CrosspointsRequest.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.traverser;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.baidu.hugegraph.api.traverser.TraversersAPI;
+import com.baidu.hugegraph.structure.constant.Direction;
+import com.baidu.hugegraph.structure.constant.Traverser;
+import com.baidu.hugegraph.util.E;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class CrosspointsRequest {
+
+    @JsonProperty("sources")
+    private VerticesArgs sources;
+    @JsonProperty("path_patterns")
+    private List<PathPattern> pathPatterns;
+    @JsonProperty("capacity")
+    private long capacity;
+    @JsonProperty("limit")
+    private long limit;
+    @JsonProperty("with_path")
+    private boolean withPath;
+    @JsonProperty("with_vertex")
+    private boolean withVertex;
+
+    private CrosspointsRequest() {
+        this.sources = null;
+        this.pathPatterns = new ArrayList<>();
+        this.capacity = Traverser.DEFAULT_CAPACITY;
+        this.limit = Traverser.DEFAULT_CROSSPOINT_LIMIT;
+        this.withPath = false;
+        this.withVertex = false;
+    }
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    @Override
+    public String toString() {
+        return String.format("CrosspointsRequest{sourceVertex=%s," +
+                             "pathPatterns=%s,capacity=%s,limit=%s," +
+                             "withPath=%s,withVertex=%s}",
+                             this.sources, this.pathPatterns, this.capacity,
+                             this.limit, this.withPath, this.withVertex);
+    }
+
+    public static class Builder {
+
+        private CrosspointsRequest request;
+        private VerticesArgs.Builder sourcesBuilder;
+        private List<PathPattern.Builder> pathPatternBuilders;
+
+        private Builder() {
+            this.request = new CrosspointsRequest();
+            this.sourcesBuilder = VerticesArgs.builder();
+            this.pathPatternBuilders = new ArrayList<>();
+        }
+
+        public VerticesArgs.Builder sources() {
+            return this.sourcesBuilder;
+        }
+
+        public PathPattern.Builder pathPatterns() {
+            PathPattern.Builder builder = new PathPattern.Builder();
+            this.pathPatternBuilders.add(builder);
+            return builder;
+        }
+
+        public Builder capacity(long capacity) {
+            TraversersAPI.checkCapacity(capacity);
+            this.request.capacity = capacity;
+            return this;
+        }
+
+        public Builder limit(long limit) {
+            TraversersAPI.checkLimit(limit);
+            this.request.limit = limit;
+            return this;
+        }
+
+        public Builder withPath(boolean withPath) {
+            this.request.withPath = withPath;
+            return this;
+        }
+
+        public Builder withVertex(boolean withVertex) {
+            this.request.withVertex = withVertex;
+            return this;
+        }
+
+        public CrosspointsRequest build() {
+            this.request.sources = this.sourcesBuilder.build();
+            for (PathPattern.Builder builder : this.pathPatternBuilders) {
+                this.request.pathPatterns.add(builder.build());
+            }
+            E.checkArgument(this.request.sources != null,
+                            "Source vertices can't be null");
+            E.checkArgument(this.request.pathPatterns != null &&
+                            !this.request.pathPatterns.isEmpty(),
+                            "Steps can't be null or empty");
+            TraversersAPI.checkCapacity(this.request.capacity);
+            TraversersAPI.checkLimit(this.request.limit);
+            return this.request;
+        }
+    }
+
+    public static class PathPattern {
+
+        @JsonProperty("steps")
+        private List<Step> steps;
+
+        private PathPattern() {
+            this.steps = new ArrayList<>();
+        }
+
+        public static class Builder {
+
+            private PathPattern pathPattern;
+            private List<Step.Builder> stepBuilders;
+
+            private Builder() {
+                this.pathPattern = new PathPattern();
+                this.stepBuilders = new ArrayList<>();
+            }
+
+            public Step.Builder steps() {
+                Step.Builder builder = new Step.Builder();
+                this.stepBuilders.add(builder);
+                return builder;
+            }
+
+            private PathPattern build() {
+                for (Step.Builder builder : this.stepBuilders) {
+                    this.pathPattern.steps.add(builder.build());
+                }
+                E.checkArgument(this.pathPattern.steps != null &&
+                                !this.pathPattern.steps.isEmpty(),
+                                "Steps of path pattern can't be empty");
+                return this.pathPattern;
+            }
+        }
+    }
+
+    public static class Step {
+
+        @JsonProperty("direction")
+        private String direction;
+        @JsonProperty("labels")
+        private List<String> labels;
+        @JsonProperty("properties")
+        private Map<String, Object> properties;
+        @JsonProperty("degree")
+        private long degree;
+
+        private Step() {
+            this.direction = null;
+            this.labels = new ArrayList<>();
+            this.properties = new HashMap<>();
+            this.degree = Traverser.DEFAULT_MAX_DEGREE;
+        }
+
+        @Override
+        public String toString() {
+            return String.format("Step{direction=%s,labels=%s,properties=%s," +
+                                 "degree=%s}", this.direction, this.labels,
+                                 this.properties, this.degree);
+        }
+
+        public static class Builder {
+
+            private Step step;
+
+            private Builder() {
+                this.step = new Step();
+            }
+
+            public Builder direction(Direction direction) {
+                this.step.direction = direction.toString();
+                return this;
+            }
+
+            public Builder labels(List<String> labels) {
+                this.step.labels = labels;
+                return this;
+            }
+
+            public Builder labels(String label) {
+                this.step.labels.add(label);
+                return this;
+            }
+
+            public Builder properties(Map<String, Object> properties) {
+                this.step.properties = properties;
+                return this;
+            }
+
+            public Builder properties(String key, Object value) {
+                this.step.properties.put(key, value);
+                return this;
+            }
+
+            public Builder degree(long degree) {
+                TraversersAPI.checkDegree(degree);
+                this.step.degree = degree;
+                return this;
+            }
+
+            private Step build() {
+                TraversersAPI.checkDegree(this.step.degree);
+                return this.step;
+            }
+        }
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/CustomizedCrosspoints.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/CustomizedCrosspoints.java
new file mode 100644
index 0000000..2d519b3
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/CustomizedCrosspoints.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.traverser;
+
+import java.util.List;
+import java.util.Set;
+
+import com.baidu.hugegraph.structure.graph.Path;
+import com.baidu.hugegraph.structure.graph.Vertex;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class CustomizedCrosspoints {
+
+    @JsonProperty
+    private List<Object> crosspoints;
+    @JsonProperty
+    private List<Path> paths;
+    @JsonProperty
+    private Set<Vertex> vertices;
+
+    public List<Object> crosspoints() {
+        return this.crosspoints;
+    }
+
+    public List<Path> paths() {
+        return this.paths;
+    }
+
+    public Set<Vertex> vertices() {
+        return this.vertices;
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/CustomizedPathsRequest.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/CustomizedPathsRequest.java
new file mode 100644
index 0000000..a466753
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/CustomizedPathsRequest.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.traverser;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.baidu.hugegraph.api.traverser.TraversersAPI;
+import com.baidu.hugegraph.structure.constant.Direction;
+import com.baidu.hugegraph.structure.constant.Traverser;
+import com.baidu.hugegraph.util.E;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import static com.baidu.hugegraph.api.API.NO_LIMIT;
+
+public class CustomizedPathsRequest {
+
+    @JsonProperty("sources")
+    private VerticesArgs sources;
+    @JsonProperty("steps")
+    private List<Step> steps;
+    @JsonProperty("sort_by")
+    private SortBy sortBy;
+    @JsonProperty("capacity")
+    private long capacity;
+    @JsonProperty("limit")
+    private long limit;
+    @JsonProperty("with_vertex")
+    private boolean withVertex;
+
+    private CustomizedPathsRequest() {
+        this.sources = null;
+        this.steps = new ArrayList<>();
+        this.sortBy = SortBy.NONE;
+        this.capacity = Traverser.DEFAULT_CAPACITY;
+        this.limit = Traverser.DEFAULT_PATHS_LIMIT;
+        this.withVertex = false;
+    }
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    @Override
+    public String toString() {
+        return String.format("CustomizedPathsRequest{sourceVertex=%s,steps=%s," +
+                             "sortBy=%s,capacity=%s,limit=%s," +
+                             "withVertex=%s}", this.sources, this.steps,
+                             this.sortBy, this.capacity, this.limit,
+                             this.withVertex);
+    }
+
+    public static class Builder {
+
+        private CustomizedPathsRequest request;
+        private VerticesArgs.Builder sourcesBuilder;
+        private List<Step.Builder> stepBuilders;
+
+        private Builder() {
+            this.request = new CustomizedPathsRequest();
+            this.sourcesBuilder = VerticesArgs.builder();
+            this.stepBuilders = new ArrayList<>();
+        }
+
+        public VerticesArgs.Builder sources() {
+            return this.sourcesBuilder;
+        }
+
+        public Step.Builder steps() {
+            Step.Builder builder = new Step.Builder();
+            this.stepBuilders.add(builder);
+            return builder;
+        }
+
+        public Builder sortBy(SortBy sortBy) {
+            this.request.sortBy = sortBy;
+            return this;
+        }
+
+        public Builder capacity(long capacity) {
+            TraversersAPI.checkCapacity(capacity);
+            this.request.capacity = capacity;
+            return this;
+        }
+
+        public Builder limit(long limit) {
+            TraversersAPI.checkLimit(limit);
+            this.request.limit = limit;
+            return this;
+        }
+
+        public Builder withVertex(boolean withVertex) {
+            this.request.withVertex = withVertex;
+            return this;
+        }
+
+        public CustomizedPathsRequest build() {
+            this.request.sources = this.sourcesBuilder.build();
+            for (Step.Builder builder : this.stepBuilders) {
+                this.request.steps.add(builder.build());
+            }
+            E.checkArgument(this.request.sources != null,
+                            "Source vertices can't be null");
+            E.checkArgument(this.request.steps != null &&
+                            !this.request.steps.isEmpty(),
+                            "Steps can't be null or empty");
+            TraversersAPI.checkCapacity(this.request.capacity);
+            TraversersAPI.checkLimit(this.request.limit);
+            return this.request;
+        }
+    }
+
+    public static class Step {
+
+        @JsonProperty("direction")
+        private String direction;
+        @JsonProperty("labels")
+        private List<String> labels;
+        @JsonProperty("properties")
+        private Map<String, Object> properties;
+        @JsonProperty("weight_by")
+        private String weightBy;
+        @JsonProperty("default_weight")
+        private double defaultWeight;
+        @JsonProperty("degree")
+        private long degree;
+        @JsonProperty("sample")
+        private long sample;
+
+        private Step() {
+            this.direction = null;
+            this.labels = new ArrayList<>();
+            this.properties = new HashMap<>();
+            this.weightBy = null;
+            this.defaultWeight = Traverser.DEFAULT_WEIGHT;
+            this.degree = Traverser.DEFAULT_MAX_DEGREE;
+            this.sample = Traverser.DEFAULT_SAMPLE;
+        }
+
+        @Override
+        public String toString() {
+            return String.format("Step{direction=%s,labels=%s,properties=%s," +
+                                 "weightBy=%s,defaultWeight=%s,degree=%s," +
+                                 "sample=%s}", this.direction, this.labels,
+                                 this.properties, this.weightBy,
+                                 this.defaultWeight, this.degree, this.sample);
+        }
+
+        public static class Builder {
+
+            private Step step;
+
+            private Builder() {
+                this.step = new Step();
+            }
+
+            public Builder direction(Direction direction) {
+                this.step.direction = direction.toString();
+                return this;
+            }
+
+            public Builder labels(List<String> labels) {
+                this.step.labels.addAll(labels);
+                return this;
+            }
+
+            public Builder labels(String... labels) {
+                this.step.labels.addAll(Arrays.asList(labels));
+                return this;
+            }
+
+            public Builder properties(Map<String, Object> properties) {
+                this.step.properties = properties;
+                return this;
+            }
+
+            public Builder properties(String key, Object value) {
+                this.step.properties.put(key, value);
+                return this;
+            }
+
+            public Builder weightBy(String property) {
+                this.step.weightBy = property;
+                return this;
+            }
+
+            public Builder defaultWeight(double weight) {
+                this.step.defaultWeight = weight;
+                return this;
+            }
+
+            public Builder degree(long degree) {
+                TraversersAPI.checkDegree(degree);
+                this.step.degree = degree;
+                return this;
+            }
+
+            public Builder sample(int sample) {
+                E.checkArgument(sample == NO_LIMIT || sample > 0,
+                                "The sample must be > 0 or == -1, but got: %s",
+                                sample);
+                this.step.sample = sample;
+                return this;
+            }
+
+            private Step build() {
+                TraversersAPI.checkDegree(this.step.degree);
+                E.checkArgument(this.step.sample > 0 ||
+                                this.step.sample == NO_LIMIT,
+                                "The sample must be > 0 or == -1, but got: %s",
+                                this.step.sample);
+                E.checkArgument(this.step.degree == NO_LIMIT ||
+                                this.step.degree >= this.step.sample,
+                                "Degree must be greater than or equal to " +
+                                "sample, but got degree %s and sample %s",
+                                this.step.degree, this.step.sample);
+                return this.step;
+            }
+        }
+    }
+
+    public enum SortBy {
+        INCR,
+        DECR,
+        NONE
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/EdgeStep.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/EdgeStep.java
new file mode 100644
index 0000000..2be861a
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/EdgeStep.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.traverser;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.baidu.hugegraph.api.API;
+import com.baidu.hugegraph.api.traverser.TraversersAPI;
+import com.baidu.hugegraph.structure.constant.Direction;
+import com.baidu.hugegraph.structure.constant.Traverser;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class EdgeStep {
+
+    @JsonProperty("direction")
+    protected Direction direction;
+    @JsonProperty("labels")
+    protected List<String> labels;
+    @JsonProperty("properties")
+    protected Map<String, Object> properties;
+    @JsonProperty("degree")
+    protected long degree;
+    @JsonProperty("skip_degree")
+    protected long skipDegree;
+
+    protected EdgeStep() {
+        this.direction = Direction.BOTH;
+        this.labels = new ArrayList<>();
+        this.properties = new HashMap<>();
+        this.degree = Traverser.DEFAULT_MAX_DEGREE;
+        this.skipDegree = Traverser.DEFAULT_SKIP_DEGREE;
+    }
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    @Override
+    public String toString() {
+        return String.format("EdgeStep{direction=%s,labels=%s,properties=%s," +
+                             "degree=%s,skipDegree=%s}",
+                             this.direction, this.labels, this.properties,
+                             this.degree, this.skipDegree);
+    }
+
+    public static class Builder {
+
+        protected EdgeStep step;
+
+        private Builder() {
+            this.step = new EdgeStep();
+        }
+
+        public EdgeStep.Builder direction(Direction direction) {
+            this.step.direction = direction;
+            return this;
+        }
+
+        public EdgeStep.Builder labels(List<String> labels) {
+            this.step.labels = labels;
+            return this;
+        }
+
+        public EdgeStep.Builder labels(String label) {
+            this.step.labels.add(label);
+            return this;
+        }
+
+        public EdgeStep.Builder properties(Map<String, Object> properties) {
+            this.step.properties = properties;
+            return this;
+        }
+
+        public EdgeStep.Builder properties(String key, Object value) {
+            this.step.properties.put(key, value);
+            return this;
+        }
+
+        public EdgeStep.Builder degree(long degree) {
+            TraversersAPI.checkDegree(degree);
+            this.step.degree = degree;
+            return this;
+        }
+
+        public EdgeStep.Builder skipDegree(long skipDegree) {
+            TraversersAPI.checkSkipDegree(skipDegree, this.step.degree,
+                                          API.NO_LIMIT);
+            this.step.skipDegree = skipDegree;
+            return this;
+        }
+
+        public EdgeStep build() {
+            TraversersAPI.checkDegree(this.step.degree);
+            TraversersAPI.checkSkipDegree(this.step.skipDegree,
+                                          this.step.degree, API.NO_LIMIT);
+            return this.step;
+        }
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/FusiformSimilarity.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/FusiformSimilarity.java
new file mode 100644
index 0000000..b5ce09c
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/FusiformSimilarity.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.traverser;
+
+import java.util.Map;
+import java.util.Set;
+
+import com.baidu.hugegraph.structure.graph.Vertex;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class FusiformSimilarity {
+
+    @JsonProperty("similars")
+    private Map<Object, Set<Similar>> similarsMap;
+    @JsonProperty("vertices")
+    private Set<Vertex> vertices;
+
+    public Map<Object, Set<Similar>> similarsMap() {
+        return this.similarsMap;
+    }
+
+    public Set<Vertex> vertices() {
+        return this.vertices;
+    }
+
+    public int size() {
+        return this.similarsMap.size();
+    }
+
+    public Map.Entry<Object, Set<Similar>> first() {
+        return this.similarsMap.entrySet().iterator().next();
+    }
+
+    public static class Similar {
+
+        @JsonProperty
+        private Object id;
+        @JsonProperty
+        private double score;
+        @JsonProperty
+        private Set<Object> intermediaries;
+
+        public Object id() {
+            return this.id;
+        }
+
+        public double score() {
+            return this.score;
+        }
+
+        public Set<Object> intermediaries() {
+            return this.intermediaries;
+        }
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/FusiformSimilarityRequest.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/FusiformSimilarityRequest.java
new file mode 100644
index 0000000..34981a9
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/FusiformSimilarityRequest.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.traverser;
+
+import com.baidu.hugegraph.api.traverser.TraversersAPI;
+import com.baidu.hugegraph.structure.constant.Direction;
+import com.baidu.hugegraph.structure.constant.Traverser;
+import com.baidu.hugegraph.util.E;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class FusiformSimilarityRequest {
+
+    @JsonProperty("sources")
+    private VerticesArgs sources;
+    @JsonProperty("label")
+    public String label;
+    @JsonProperty("direction")
+    public String direction;
+    @JsonProperty("min_neighbors")
+    public int minNeighbors;
+    @JsonProperty("alpha")
+    public double alpha;
+    @JsonProperty("min_similars")
+    public int minSimilars;
+    @JsonProperty("top")
+    public int top;
+    @JsonProperty("group_property")
+    public String groupProperty;
+    @JsonProperty("min_groups")
+    public int minGroups;
+    @JsonProperty("max_degree")
+    public long degree;
+    @JsonProperty("capacity")
+    public long capacity;
+    @JsonProperty("limit")
+    public long limit;
+    @JsonProperty("with_intermediary")
+    public boolean withIntermediary;
+    @JsonProperty("with_vertex")
+    public boolean withVertex;
+
+    private FusiformSimilarityRequest() {
+        this.sources = null;
+        this.label = null;
+        this.direction = null;
+        this.minNeighbors = 0;
+        this.degree = Traverser.DEFAULT_MAX_DEGREE;
+        this.alpha = 1.0f;
+        this.minSimilars = 1;
+        this.top = 0;
+        this.groupProperty = null;
+        this.minGroups = 0;
+        this.capacity = Traverser.DEFAULT_CAPACITY;
+        this.limit = Traverser.DEFAULT_PATHS_LIMIT;
+        this.withIntermediary = false;
+        this.withVertex = false;
+    }
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    @Override
+    public String toString() {
+        return String.format("FusiformSimilarityRequest{sourceVertex=%s," +
+                             "label=%s,direction=%s,minNeighbors=%s," +
+                             "alpha=%s,minSimilars=%s,top=%s," +
+                             "groupProperty=%s,minGroups=%s," +
+                             "degree=%s,capacity=%s,limit=%s," +
+                             "withIntermediary=%s,withVertex=%s}",
+                             this.sources, this.label, this.direction,
+                             this.minNeighbors, this.alpha, this.minSimilars,
+                             this.top, this.groupProperty, this.minGroups,
+                             this.degree, this.capacity, this.limit,
+                             this.withIntermediary, this.withVertex);
+    }
+
+    public static class Builder {
+
+        private FusiformSimilarityRequest request;
+        private VerticesArgs.Builder sourcesBuilder;
+
+        private Builder() {
+            this.request = new FusiformSimilarityRequest();
+            this.sourcesBuilder = VerticesArgs.builder();
+        }
+
+        public VerticesArgs.Builder sources() {
+            return this.sourcesBuilder;
+        }
+
+        public Builder label(String label) {
+            this.request.label = label;
+            return this;
+        }
+
+        public Builder direction(Direction direction) {
+            this.request.direction = direction.toString();
+            return this;
+        }
+
+        public Builder minNeighbors(int minNeighbors) {
+            TraversersAPI.checkPositive(minNeighbors, "min neighbor count");
+            this.request.minNeighbors = minNeighbors;
+            return this;
+        }
+
+        public Builder alpha(double alpha) {
+            TraversersAPI.checkAlpha(alpha);
+            this.request.alpha = alpha;
+            return this;
+        }
+
+        public Builder minSimilars(int minSimilars) {
+            TraversersAPI.checkPositive(minSimilars, "min similar count");
+            this.request.minSimilars = minSimilars;
+            return this;
+        }
+
+        public Builder top(int top) {
+            TraversersAPI.checkPositive(top, "top");
+            this.request.top = top;
+            return this;
+        }
+
+        public Builder groupProperty(String groupProperty) {
+            E.checkArgumentNotNull(groupProperty,
+                                   "The group property can't be null");
+            this.request.groupProperty = groupProperty;
+            return this;
+        }
+
+        public Builder minGroups(int minGroups) {
+            TraversersAPI.checkPositive(minGroups, "min group count");
+            this.request.minGroups = minGroups;
+            return this;
+        }
+
+        public Builder degree(long degree) {
+            TraversersAPI.checkDegree(degree);
+            this.request.degree = degree;
+            return this;
+        }
+
+        public Builder capacity(long capacity) {
+            TraversersAPI.checkCapacity(capacity);
+            this.request.capacity = capacity;
+            return this;
+        }
+
+        public Builder limit(long limit) {
+            TraversersAPI.checkLimit(limit);
+            this.request.limit = limit;
+            return this;
+        }
+
+        public Builder withIntermediary(boolean withIntermediary) {
+            this.request.withIntermediary = withIntermediary;
+            return this;
+        }
+
+        public Builder withVertex(boolean withVertex) {
+            this.request.withVertex = withVertex;
+            return this;
+        }
+
+        public FusiformSimilarityRequest build() {
+            this.request.sources = this.sourcesBuilder.build();
+            E.checkArgument(this.request.sources != null,
+                            "Source vertices can't be null");
+            TraversersAPI.checkPositive(request.minNeighbors,
+                                        "min neighbor count");
+            TraversersAPI.checkPositive(request.minSimilars,
+                                        "min similar count");
+            if (request.groupProperty != null) {
+                TraversersAPI.checkPositive(request.minGroups,
+                                            "min group count");
+            }
+            TraversersAPI.checkAlpha(request.alpha);
+            TraversersAPI.checkDegree(request.degree);
+            TraversersAPI.checkCapacity(this.request.capacity);
+            TraversersAPI.checkLimit(this.request.limit);
+            return this.request;
+        }
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/Kneighbor.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/Kneighbor.java
new file mode 100644
index 0000000..512700f
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/Kneighbor.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.traverser;
+
+import java.util.List;
+import java.util.Set;
+
+import com.baidu.hugegraph.structure.graph.Path;
+import com.baidu.hugegraph.structure.graph.Vertex;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class Kneighbor {
+
+    @JsonProperty
+    private int size;
+    @JsonProperty("kneighbor")
+    private Set<Object> ids;
+    @JsonProperty
+    private List<Path> paths;
+    @JsonProperty
+    private Set<Vertex> vertices;
+
+    public int size() {
+        return this.size;
+    }
+
+    public Set<Object> ids() {
+        return this.ids;
+    }
+
+    public List<Path> paths() {
+        return this.paths;
+    }
+
+    public Set<Vertex> vertices() {
+        return this.vertices;
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/KneighborRequest.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/KneighborRequest.java
new file mode 100644
index 0000000..7907cc5
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/KneighborRequest.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.traverser;
+
+import com.baidu.hugegraph.api.traverser.TraversersAPI;
+import com.baidu.hugegraph.structure.constant.Traverser;
+import com.baidu.hugegraph.util.E;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class KneighborRequest {
+
+    @JsonProperty("source")
+    private Object source;
+    @JsonProperty("step")
+    public EdgeStep step;
+    @JsonProperty("max_depth")
+    public int maxDepth;
+    @JsonProperty("count_only")
+    public boolean countOnly = false;
+    @JsonProperty("limit")
+    public long limit = Traverser.DEFAULT_LIMIT;
+    @JsonProperty("with_vertex")
+    public boolean withVertex = false;
+    @JsonProperty("with_path")
+    public boolean withPath = false;
+
+    private KneighborRequest() {
+        this.source = null;
+        this.step = null;
+        this.maxDepth = Traverser.DEFAULT_MAX_DEPTH;
+        this.countOnly = false;
+        this.limit = Traverser.DEFAULT_PATHS_LIMIT;
+        this.withVertex = false;
+        this.withPath = false;
+    }
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    @Override
+    public String toString() {
+        return String.format("KneighborRequest{source=%s,step=%s,maxDepth=%s" +
+                             "countOnly=%s,limit=%s,withVertex=%s,withPath=%s}",
+                             this.source, this.step, this.maxDepth,
+                             this.countOnly, this.limit,
+                             this.withVertex, this.withPath);
+    }
+
+    public static class Builder {
+
+        private KneighborRequest request;
+        private EdgeStep.Builder stepBuilder;
+
+        private Builder() {
+            this.request = new KneighborRequest();
+            this.stepBuilder = EdgeStep.builder();
+        }
+
+        public Builder source(Object source) {
+            E.checkNotNull(source, "source");
+            this.request.source = source;
+            return this;
+        }
+
+        public EdgeStep.Builder step() {
+            EdgeStep.Builder builder = EdgeStep.builder();
+            this.stepBuilder = builder;
+            return builder;
+        }
+
+        public Builder maxDepth(int maxDepth) {
+            TraversersAPI.checkPositive(maxDepth, "max depth");
+            this.request.maxDepth = maxDepth;
+            return this;
+        }
+
+        public Builder countOnly(boolean countOnly) {
+            this.request.countOnly = countOnly;
+            return this;
+        }
+
+        public Builder limit(long limit) {
+            TraversersAPI.checkLimit(limit);
+            this.request.limit = limit;
+            return this;
+        }
+
+        public Builder withVertex(boolean withVertex) {
+            this.request.withVertex = withVertex;
+            return this;
+        }
+
+        public Builder withPath(boolean withPath) {
+            this.request.withPath = withPath;
+            return this;
+        }
+
+        public KneighborRequest build() {
+            E.checkNotNull(this.request.source, "The source can't be null");
+            this.request.step = this.stepBuilder.build();
+            E.checkNotNull(this.request.step, "step");
+            TraversersAPI.checkPositive(this.request.maxDepth, "max depth");
+            TraversersAPI.checkLimit(this.request.limit);
+            if (this.request.countOnly) {
+                E.checkArgument(!this.request.withVertex &&
+                                !this.request.withPath,
+                                "Can't return vertex or path " +
+                                "when count only is true");
+            }
+            return this.request;
+        }
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/Kout.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/Kout.java
new file mode 100644
index 0000000..ade813f
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/Kout.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.traverser;
+
+import java.util.List;
+import java.util.Set;
+
+import com.baidu.hugegraph.structure.graph.Path;
+import com.baidu.hugegraph.structure.graph.Vertex;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class Kout {
+
+    @JsonProperty
+    private int size;
+    @JsonProperty("kout")
+    private Set<Object> ids;
+    @JsonProperty
+    private List<Path> paths;
+    @JsonProperty
+    private Set<Vertex> vertices;
+
+    public int size() {
+        return this.size;
+    }
+
+    public Set<Object> ids() {
+        return this.ids;
+    }
+
+    public List<Path> paths() {
+        return this.paths;
+    }
+
+    public Set<Vertex> vertices() {
+        return this.vertices;
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/KoutRequest.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/KoutRequest.java
new file mode 100644
index 0000000..0217a03
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/KoutRequest.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.traverser;
+
+import com.baidu.hugegraph.api.traverser.TraversersAPI;
+import com.baidu.hugegraph.structure.constant.Traverser;
+import com.baidu.hugegraph.util.E;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class KoutRequest {
+
+    @JsonProperty("source")
+    private Object source;
+    @JsonProperty("step")
+    public EdgeStep step;
+    @JsonProperty("max_depth")
+    public int maxDepth;
+    @JsonProperty("nearest")
+    public boolean nearest = true;
+    @JsonProperty("count_only")
+    public boolean countOnly = false;
+    @JsonProperty("capacity")
+    public long capacity = Traverser.DEFAULT_CAPACITY;
+    @JsonProperty("limit")
+    public long limit = Traverser.DEFAULT_LIMIT;
+    @JsonProperty("with_vertex")
+    public boolean withVertex = false;
+    @JsonProperty("with_path")
+    public boolean withPath = false;
+
+    private KoutRequest() {
+        this.source = null;
+        this.step = null;
+        this.maxDepth = Traverser.DEFAULT_MAX_DEPTH;
+        this.nearest = true;
+        this.countOnly = false;
+        this.capacity = Traverser.DEFAULT_CAPACITY;
+        this.limit = Traverser.DEFAULT_PATHS_LIMIT;
+        this.withVertex = false;
+        this.withPath = false;
+    }
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    @Override
+    public String toString() {
+        return String.format("KoutRequest{source=%s,step=%s,maxDepth=%s" +
+                             "nearest=%s,countOnly=%s,capacity=%s," +
+                             "limit=%s,withVertex=%s,withPath=%s}",
+                             this.source, this.step, this.maxDepth,
+                             this.nearest, this.countOnly, this.capacity,
+                             this.limit, this.withVertex, this.withPath);
+    }
+
+    public static class Builder {
+
+        private KoutRequest request;
+        private EdgeStep.Builder stepBuilder;
+
+        private Builder() {
+                this.request = new KoutRequest();
+                this.stepBuilder = EdgeStep.builder();
+        }
+
+        public Builder source(Object source) {
+            E.checkNotNull(source, "source");
+            this.request.source = source;
+            return this;
+        }
+
+        public EdgeStep.Builder step() {
+            EdgeStep.Builder builder = EdgeStep.builder();
+            this.stepBuilder = builder;
+            return builder;
+        }
+
+        public Builder maxDepth(int maxDepth) {
+            TraversersAPI.checkPositive(maxDepth, "max depth");
+            this.request.maxDepth = maxDepth;
+            return this;
+        }
+
+        public Builder nearest(boolean nearest) {
+            this.request.nearest = nearest;
+            return this;
+        }
+
+        public Builder countOnly(boolean countOnly) {
+            this.request.countOnly = countOnly;
+            return this;
+        }
+
+        public Builder capacity(long capacity) {
+            TraversersAPI.checkCapacity(capacity);
+            this.request.capacity = capacity;
+            return this;
+        }
+
+        public Builder limit(long limit) {
+            TraversersAPI.checkLimit(limit);
+            this.request.limit = limit;
+            return this;
+        }
+
+        public Builder withVertex(boolean withVertex) {
+            this.request.withVertex = withVertex;
+            return this;
+        }
+
+        public Builder withPath(boolean withPath) {
+            this.request.withPath = withPath;
+            return this;
+        }
+
+        public KoutRequest build() {
+            E.checkNotNull(this.request.source, "The source can't be null");
+            this.request.step = this.stepBuilder.build();
+            E.checkNotNull(this.request.step, "step");
+            TraversersAPI.checkPositive(this.request.maxDepth, "max depth");
+            TraversersAPI.checkCapacity(this.request.capacity);
+            TraversersAPI.checkLimit(this.request.limit);
+            if (this.request.countOnly) {
+                E.checkArgument(!this.request.withVertex &&
+                                !this.request.withPath,
+                                "Can't return vertex or path " +
+                                "when count only is true");
+            }
+            return this.request;
+        }
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/MultiNodeShortestPathRequest.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/MultiNodeShortestPathRequest.java
new file mode 100644
index 0000000..9bd67d7
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/MultiNodeShortestPathRequest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.traverser;
+
+import com.baidu.hugegraph.api.traverser.TraversersAPI;
+import com.baidu.hugegraph.structure.constant.Traverser;
+import com.baidu.hugegraph.util.E;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class MultiNodeShortestPathRequest {
+
+    @JsonProperty("vertices")
+    public VerticesArgs vertices;
+    @JsonProperty("step")
+    public EdgeStep step;
+    @JsonProperty("max_depth")
+    public int maxDepth;
+    @JsonProperty("capacity")
+    public long capacity = Traverser.DEFAULT_CAPACITY;
+    @JsonProperty("with_vertex")
+    public boolean withVertex = false;
+
+    private MultiNodeShortestPathRequest() {
+        this.vertices = null;
+        this.step = null;
+        this.maxDepth = Traverser.DEFAULT_MAX_DEPTH;
+        this.capacity = Traverser.DEFAULT_CAPACITY;
+        this.withVertex = false;
+    }
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    @Override
+    public String toString() {
+        return String.format("MultiNodeShortestPathRequest{vertices=%s," +
+                             "step=%s,maxDepth=%s,capacity=%s,withVertex=%s}",
+                             this.vertices, this.step, this.maxDepth,
+                             this.capacity, this.withVertex);
+    }
+
+    public static class Builder {
+
+        private MultiNodeShortestPathRequest request;
+        private VerticesArgs.Builder verticesBuilder;
+        private EdgeStep.Builder stepBuilder;
+
+        private Builder() {
+            this.request = new MultiNodeShortestPathRequest();
+            this.verticesBuilder = VerticesArgs.builder();
+            this.stepBuilder = EdgeStep.builder();
+        }
+
+        public VerticesArgs.Builder vertices() {
+            return this.verticesBuilder;
+        }
+
+        public EdgeStep.Builder step() {
+            EdgeStep.Builder builder = EdgeStep.builder();
+            this.stepBuilder = builder;
+            return builder;
+        }
+
+        public Builder maxDepth(int maxDepth) {
+            TraversersAPI.checkPositive(maxDepth, "max depth");
+            this.request.maxDepth = maxDepth;
+            return this;
+        }
+
+        public Builder capacity(long capacity) {
+            TraversersAPI.checkCapacity(capacity);
+            this.request.capacity = capacity;
+            return this;
+        }
+
+        public Builder withVertex(boolean withVertex) {
+            this.request.withVertex = withVertex;
+            return this;
+        }
+
+        public MultiNodeShortestPathRequest build() {
+            this.request.vertices = this.verticesBuilder.build();
+            E.checkArgument(this.request.vertices != null,
+                            "The vertices can't be null");
+            this.request.step = this.stepBuilder.build();
+            E.checkArgument(this.request.step != null,
+                            "The step can't be null");
+            TraversersAPI.checkPositive(this.request.maxDepth, "max depth");
+            TraversersAPI.checkCapacity(this.request.capacity);
+            return this.request;
+        }
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/PathsRequest.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/PathsRequest.java
new file mode 100644
index 0000000..5b77287
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/PathsRequest.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.traverser;
+
+import com.baidu.hugegraph.api.traverser.TraversersAPI;
+import com.baidu.hugegraph.structure.constant.Traverser;
+import com.baidu.hugegraph.util.E;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class PathsRequest {
+
+    @JsonProperty("sources")
+    private VerticesArgs sources;
+    @JsonProperty("targets")
+    private VerticesArgs targets;
+    @JsonProperty("step")
+    public EdgeStep step;
+    @JsonProperty("max_depth")
+    public int depth;
+    @JsonProperty("nearest")
+    public boolean nearest = false;
+    @JsonProperty("capacity")
+    public long capacity = Traverser.DEFAULT_CAPACITY;
+    @JsonProperty("limit")
+    public long limit = Traverser.DEFAULT_LIMIT;
+    @JsonProperty("with_vertex")
+    public boolean withVertex = false;
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    @Override
+    public String toString() {
+        return String.format("PathRequest{sources=%s,targets=%s,step=%s," +
+                             "maxDepth=%s,nearest=%s,capacity=%s," +
+                             "limit=%s,withVertex=%s}",
+                             this.sources, this.targets, this.step, this.depth,
+                             this.nearest, this.capacity,
+                             this.limit, this.withVertex);
+    }
+
+    public static class Builder {
+
+        private PathsRequest request;
+        private EdgeStep.Builder stepBuilder;
+        private VerticesArgs.Builder sourcesBuilder;
+        private VerticesArgs.Builder targetsBuilder;
+
+        private Builder() {
+            this.request = new PathsRequest();
+            this.stepBuilder = EdgeStep.builder();
+            this.sourcesBuilder = VerticesArgs.builder();
+            this.targetsBuilder = VerticesArgs.builder();
+        }
+
+        public VerticesArgs.Builder sources() {
+            return this.sourcesBuilder;
+        }
+
+        public VerticesArgs.Builder targets() {
+            return this.targetsBuilder;
+        }
+
+        public EdgeStep.Builder step() {
+            EdgeStep.Builder builder = EdgeStep.builder();
+            this.stepBuilder = builder;
+            return builder;
+        }
+
+        public PathsRequest.Builder maxDepth(int maxDepth) {
+            TraversersAPI.checkPositive(maxDepth, "max depth");
+            this.request.depth = maxDepth;
+            return this;
+        }
+
+        public PathsRequest.Builder nearest(boolean nearest) {
+            this.request.nearest = nearest;
+            return this;
+        }
+
+        public PathsRequest.Builder capacity(long capacity) {
+            TraversersAPI.checkCapacity(capacity);
+            this.request.capacity = capacity;
+            return this;
+        }
+
+        public PathsRequest.Builder limit(long limit) {
+            TraversersAPI.checkLimit(limit);
+            this.request.limit = limit;
+            return this;
+        }
+
+        public PathsRequest.Builder withVertex(boolean withVertex) {
+            this.request.withVertex = withVertex;
+            return this;
+        }
+
+        public PathsRequest build() {
+            this.request.sources = this.sourcesBuilder.build();
+            E.checkArgument(this.request.sources != null,
+                            "Source vertices can't be null");
+            this.request.targets = this.targetsBuilder.build();
+            E.checkArgument(this.request.targets != null,
+                            "Target vertices can't be null");
+            this.request.step = this.stepBuilder.build();
+            E.checkNotNull(this.request.step, "The steps can't be null");
+            TraversersAPI.checkPositive(this.request.depth, "max depth");
+            TraversersAPI.checkCapacity(this.request.capacity);
+            TraversersAPI.checkLimit(this.request.limit);
+            return this.request;
+        }
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/PathsWithVertices.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/PathsWithVertices.java
new file mode 100644
index 0000000..6668816
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/PathsWithVertices.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.traverser;
+
+import java.util.List;
+import java.util.Set;
+
+import com.baidu.hugegraph.structure.graph.Vertex;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class PathsWithVertices {
+
+    @JsonProperty
+    private List<Paths> paths;
+    @JsonProperty
+    private Set<Vertex> vertices;
+
+    public List<Paths> paths() {
+        return this.paths;
+    }
+
+    public Set<Vertex> vertices() {
+        return this.vertices;
+    }
+
+    public static class Paths {
+
+        @JsonProperty
+        private List<Object> objects;
+        @JsonProperty
+        private List<Double> weights;
+
+        public List<Object> objects() {
+            return this.objects;
+        }
+
+        public List<Double> weights() {
+            return this.weights;
+        }
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/Ranks.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/Ranks.java
new file mode 100644
index 0000000..a8bb315
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/Ranks.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.traverser;
+
+import java.util.LinkedHashMap;
+
+public class Ranks extends LinkedHashMap<Object, Double> {
+
+    private static final long serialVersionUID = -517025731560925613L;
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/RepeatEdgeStep.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/RepeatEdgeStep.java
new file mode 100644
index 0000000..1783b7a
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/RepeatEdgeStep.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.traverser;
+
+import java.util.List;
+import java.util.Map;
+
+import com.baidu.hugegraph.api.API;
+import com.baidu.hugegraph.api.traverser.TraversersAPI;
+import com.baidu.hugegraph.structure.constant.Direction;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class RepeatEdgeStep extends EdgeStep {
+
+    @JsonProperty("max_times")
+    public int maxTimes = 1;
+
+    private RepeatEdgeStep() {
+        super();
+        this.maxTimes = 1;
+    }
+
+    public static Builder repeatStepBuilder() {
+        return new Builder();
+    }
+
+    @Override
+    public String toString() {
+        return String.format("RepeatEdgeStep{direction=%s,labels=%s," +
+                             "properties=%s,degree=%s,skipDegree=%s," +
+                             "maxTimes=%s}",
+                             this.direction, this.labels, this.properties,
+                             this.degree, this.skipDegree, this.maxTimes);
+    }
+
+    public static class Builder {
+
+        protected RepeatEdgeStep step;
+
+        private Builder() {
+            this.step = new RepeatEdgeStep();
+        }
+
+        public Builder direction(Direction direction) {
+            this.step.direction = direction;
+            return this;
+        }
+
+        public Builder labels(List<String> labels) {
+            this.step.labels = labels;
+            return this;
+        }
+
+        public Builder labels(String label) {
+            this.step.labels.add(label);
+            return this;
+        }
+
+        public Builder properties(Map<String, Object> properties) {
+            this.step.properties = properties;
+            return this;
+        }
+
+        public Builder properties(String key, Object value) {
+            this.step.properties.put(key, value);
+            return this;
+        }
+
+        public Builder maxTimes(int maxTimes) {
+            this.step.maxTimes = maxTimes;
+            return this;
+        }
+
+        public Builder degree(long degree) {
+            TraversersAPI.checkDegree(degree);
+            this.step.degree = degree;
+            return this;
+        }
+
+        public Builder skipDegree(long skipDegree) {
+            TraversersAPI.checkSkipDegree(skipDegree, this.step.degree,
+                                          API.NO_LIMIT);
+            this.step.skipDegree = skipDegree;
+            return this;
+        }
+
+        public RepeatEdgeStep build() {
+            TraversersAPI.checkDegree(this.step.degree);
+            TraversersAPI.checkSkipDegree(this.step.skipDegree,
+                                          this.step.degree, API.NO_LIMIT);
+            TraversersAPI.checkPositive(this.step.maxTimes, "max times");
+            return this.step;
+        }
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/SingleSourceJaccardSimilarityRequest.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/SingleSourceJaccardSimilarityRequest.java
new file mode 100644
index 0000000..f5207b0
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/SingleSourceJaccardSimilarityRequest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.traverser;
+
+import com.baidu.hugegraph.api.traverser.TraversersAPI;
+import com.baidu.hugegraph.structure.constant.Traverser;
+import com.baidu.hugegraph.util.E;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class SingleSourceJaccardSimilarityRequest {
+
+    @JsonProperty("vertex")
+    private Object vertex;
+    @JsonProperty("step")
+    public EdgeStep step;
+    @JsonProperty("top")
+    public int top = 10;
+    @JsonProperty("capacity")
+    public long capacity = Traverser.DEFAULT_CAPACITY;
+
+    private SingleSourceJaccardSimilarityRequest() {
+        this.vertex = null;
+        this.step = null;
+        this.top = 10;
+        this.capacity = Traverser.DEFAULT_CAPACITY;
+    }
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    @Override
+    public String toString() {
+        return String.format("SingleSourceJaccardSimilarityRequest{vertex=%s," +
+                             "step=%s,top=%s,capacity=%s}",
+                             this.vertex, this.step, this.top, this.capacity);
+    }
+
+    public static class Builder {
+
+        private SingleSourceJaccardSimilarityRequest request;
+        private EdgeStep.Builder stepBuilder;
+
+        private Builder() {
+            this.request = new SingleSourceJaccardSimilarityRequest();
+            this.stepBuilder = EdgeStep.builder();
+        }
+
+        public Builder vertex(Object vertex) {
+            E.checkNotNull(vertex, "vertex");
+            this.request.vertex = vertex;
+            return this;
+        }
+
+        public EdgeStep.Builder step() {
+            EdgeStep.Builder builder = EdgeStep.builder();
+            this.stepBuilder = builder;
+            return builder;
+        }
+
+        public Builder top(int top) {
+            TraversersAPI.checkPositive(top, "top");
+            this.request.top = top;
+            return this;
+        }
+
+        public Builder capacity(long capacity) {
+            TraversersAPI.checkCapacity(capacity);
+            this.request.capacity = capacity;
+            return this;
+        }
+
+        public SingleSourceJaccardSimilarityRequest build() {
+            E.checkArgument(this.request.vertex != null,
+                            "The vertex can't be null");
+            this.request.step = this.stepBuilder.build();
+            E.checkNotNull(this.request.step, "step");
+            TraversersAPI.checkCapacity(this.request.capacity);
+            E.checkArgument(this.request.top >= 0,
+                            "The top must be >= 0, but got: %s",
+                            this.request.top);
+            return this.request;
+        }
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/TemplatePathsRequest.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/TemplatePathsRequest.java
new file mode 100644
index 0000000..ee24d1b
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/TemplatePathsRequest.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.traverser;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.baidu.hugegraph.api.traverser.TraversersAPI;
+import com.baidu.hugegraph.structure.constant.Traverser;
+import com.baidu.hugegraph.util.E;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class TemplatePathsRequest {
+
+    @JsonProperty("sources")
+    private VerticesArgs sources;
+    @JsonProperty("targets")
+    private VerticesArgs targets;
+    @JsonProperty("steps")
+    public List<RepeatEdgeStep> steps;
+    @JsonProperty("with_ring")
+    public boolean withRing = false;
+    @JsonProperty("capacity")
+    public long capacity = Traverser.DEFAULT_CAPACITY;
+    @JsonProperty("limit")
+    public long limit = Traverser.DEFAULT_PATHS_LIMIT;
+    @JsonProperty("with_vertex")
+    public boolean withVertex = false;
+
+    private TemplatePathsRequest() {
+        this.sources = null;
+        this.targets = null;
+        this.steps = new ArrayList<>();
+        this.withRing = false;
+        this.capacity = Traverser.DEFAULT_CAPACITY;
+        this.limit = Traverser.DEFAULT_PATHS_LIMIT;
+        this.withVertex = false;
+    }
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    @Override
+    public String toString() {
+        return String.format("TemplatePathsRequest{sources=%s,targets=%s," +
+                             "steps=%s,withRing=%s,capacity=%s,limit=%s," +
+                             "withVertex=%s}", this.sources, this.targets,
+                             this.steps, this.withRing, this.capacity,
+                             this.limit, this.withVertex);
+    }
+
+    public static class Builder {
+
+        private TemplatePathsRequest request;
+        private VerticesArgs.Builder sourcesBuilder;
+        private VerticesArgs.Builder targetsBuilder;
+        private List<RepeatEdgeStep.Builder> stepBuilders;
+
+        private Builder() {
+            this.request = new TemplatePathsRequest();
+            this.sourcesBuilder = VerticesArgs.builder();
+            this.targetsBuilder = VerticesArgs.builder();
+            this.stepBuilders = new ArrayList<>();
+        }
+
+        public VerticesArgs.Builder sources() {
+            return this.sourcesBuilder;
+        }
+
+        public VerticesArgs.Builder targets() {
+            return this.targetsBuilder;
+        }
+
+        public RepeatEdgeStep.Builder steps() {
+            RepeatEdgeStep.Builder builder = RepeatEdgeStep.repeatStepBuilder();
+            this.stepBuilders.add(builder);
+            return builder;
+        }
+
+        public Builder withRing(boolean withRing) {
+            this.request.withRing = withRing;
+            return this;
+        }
+
+        public Builder capacity(long capacity) {
+            TraversersAPI.checkCapacity(capacity);
+            this.request.capacity = capacity;
+            return this;
+        }
+
+        public Builder limit(long limit) {
+            TraversersAPI.checkLimit(limit);
+            this.request.limit = limit;
+            return this;
+        }
+
+        public Builder withVertex(boolean withVertex) {
+            this.request.withVertex = withVertex;
+            return this;
+        }
+
+        public TemplatePathsRequest build() {
+            this.request.sources = this.sourcesBuilder.build();
+            E.checkArgument(this.request.sources != null,
+                            "Source vertices can't be null");
+            this.request.targets = this.targetsBuilder.build();
+            E.checkArgument(this.request.targets != null,
+                            "Target vertices can't be null");
+            for (RepeatEdgeStep.Builder builder : this.stepBuilders) {
+                this.request.steps.add(builder.build());
+            }
+            E.checkArgument(this.request.steps != null &&
+                            !this.request.steps.isEmpty(),
+                            "The steps can't be null or empty");
+            TraversersAPI.checkCapacity(this.request.capacity);
+            TraversersAPI.checkLimit(this.request.limit);
+            return this.request;
+        }
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/VerticesArgs.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/VerticesArgs.java
new file mode 100644
index 0000000..4a6a193
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/VerticesArgs.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.traverser;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import com.baidu.hugegraph.util.E;
+
+public class VerticesArgs {
+
+    public Set<Object> ids;
+    public String label;
+    public Map<String, Object> properties;
+
+    private VerticesArgs() {
+        this.ids = new HashSet<>();
+        this.label = null;
+        this.properties = new HashMap<>();
+    }
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    @Override
+    public String toString() {
+        return String.format("VerticesArgs{ids=%s,label=%s,properties=%s}",
+                             this.ids, this.label, this.properties);
+    }
+
+    public static class Builder {
+
+        private VerticesArgs vertices;
+
+        private Builder() {
+            this.vertices = new VerticesArgs();
+        }
+
+        public Builder ids(Set<Object> ids) {
+            this.vertices.ids.addAll(ids);
+            return this;
+        }
+
+        public Builder ids(Object... ids) {
+            this.vertices.ids.addAll(Arrays.asList(ids));
+            return this;
+        }
+
+        public Builder label(String label) {
+            this.vertices.label = label;
+            return this;
+        }
+
+        public Builder properties(Map<String, Object> properties) {
+            this.vertices.properties = properties;
+            return this;
+        }
+
+        public Builder property(String key, Object value) {
+            this.vertices.properties.put(key, value);
+            return this;
+        }
+
+        protected VerticesArgs build() {
+            E.checkArgument(!((this.vertices.ids == null ||
+                               this.vertices.ids.isEmpty()) &&
+                              (this.vertices.properties == null ||
+                               this.vertices.properties.isEmpty()) &&
+                              (this.vertices.label == null ||
+                               this.vertices.label.isEmpty())),
+                            "No vertices provided");
+            return this.vertices;
+        }
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/WeightedPath.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/WeightedPath.java
new file mode 100644
index 0000000..2a50e62
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/WeightedPath.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.traverser;
+
+import java.util.List;
+import java.util.Set;
+
+import com.baidu.hugegraph.structure.graph.Vertex;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class WeightedPath {
+
+    @JsonProperty
+    private Path path;
+    @JsonProperty
+    private Set<Vertex> vertices;
+
+    public Path path() {
+        return this.path;
+    }
+
+    public Set<Vertex> vertices() {
+        return this.vertices;
+    }
+
+    public static class Path {
+
+        @JsonProperty
+        private double weight;
+        @JsonProperty
+        private List<Object> vertices;
+
+        public double weight() {
+            return this.weight;
+        }
+
+        public List<Object> vertices() {
+            return this.vertices;
+        }
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/WeightedPaths.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/WeightedPaths.java
new file mode 100644
index 0000000..c7ce572
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/traverser/WeightedPaths.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.traverser;
+
+import java.util.Map;
+import java.util.Set;
+
+import com.baidu.hugegraph.structure.graph.Vertex;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class WeightedPaths {
+
+    @JsonProperty
+    private Map<Object, WeightedPath.Path> paths;
+    @JsonProperty
+    private Set<Vertex> vertices;
+
+    public Map<Object, WeightedPath.Path> paths() {
+        return this.paths;
+    }
+
+    public Set<Vertex> vertices() {
+        return this.vertices;
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/version/Versions.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/version/Versions.java
new file mode 100644
index 0000000..6324422
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/structure/version/Versions.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.structure.version;
+
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class Versions {
+
+    @JsonProperty
+    private Map<String, String> versions;
+
+    public String get(String name) {
+        return this.versions.get(name);
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/util/CommonUtil.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/util/CommonUtil.java
new file mode 100644
index 0000000..4506a33
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/util/CommonUtil.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.util;
+
+import java.util.Map;
+
+public final class CommonUtil {
+
+    public static void checkMapClass(Object object, Class<?> kClass,
+                                     Class<?> vClass) {
+        E.checkArgumentNotNull(object, "The object can't be null");
+        E.checkArgument(object instanceof Map,
+                        "The object must be instance of Map, but got '%s'(%s)",
+                        object, object.getClass());
+        E.checkArgumentNotNull(kClass, "The key class can't be null");
+        E.checkArgumentNotNull(vClass, "The value class can't be null");
+        Map<?, ?> map = (Map<?, ?>) object;
+        for (Map.Entry<?, ?> entry : map.entrySet()) {
+            Object key = entry.getKey();
+            Object value = entry.getValue();
+            E.checkState(kClass.isAssignableFrom(key.getClass()),
+                         "The map key must be instance of %s, " +
+                         "but got '%s'(%s)", kClass, key, key.getClass());
+            E.checkState(vClass.isAssignableFrom(value.getClass()),
+                         "The map value must be instance of %s, " +
+                         "but got '%s'(%s)", vClass, value, value.getClass());
+        }
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/util/IdUtil.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/util/IdUtil.java
new file mode 100644
index 0000000..9ebe91d
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/util/IdUtil.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.util;
+
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * Copied from HugeGraph(https://github.com/hugegraph/hugegraph)
+ */
+public final class IdUtil {
+
+    public static String escape(char splitor, char escape, String... values) {
+        int length = values.length + 4;
+        for (String value : values) {
+            length += value.length();
+        }
+        StringBuilder escaped = new StringBuilder(length);
+        // Do escape for every item in values
+        for (String value : values) {
+            if (escaped.length() > 0) {
+                escaped.append(splitor);
+            }
+
+            if (value.indexOf(splitor) == -1) {
+                escaped.append(value);
+                continue;
+            }
+
+            // Do escape for current item
+            for (int i = 0, n = value.length(); i < n; i++) {
+                char ch = value.charAt(i);
+                if (ch == splitor) {
+                    escaped.append(escape);
+                }
+                escaped.append(ch);
+            }
+        }
+        return escaped.toString();
+    }
+
+    public static String[] unescape(String id, String splitor, String escape) {
+        /*
+         * Note that the `splitor`/`escape` maybe special characters in regular
+         * expressions, but this is a frequently called method, for faster
+         * execution, we forbid the use of special characters as delimiter
+         * or escape sign.
+         * The `limit` param -1 in split method can ensure empty string be
+         * splited to a part.
+         */
+        String[] parts = id.split("(?<!" + escape + ")" + splitor, -1);
+        for (int i = 0; i < parts.length; i++) {
+            parts[i] = StringUtils.replace(parts[i], escape + splitor,
+                                           splitor);
+        }
+        return parts;
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/util/JsonUtil.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/util/JsonUtil.java
new file mode 100644
index 0000000..d596402
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/util/JsonUtil.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.util;
+
+import java.io.IOException;
+
+import com.baidu.hugegraph.rest.SerializeException;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.Module;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+public final class JsonUtil {
+
+    private static final ObjectMapper MAPPER = new ObjectMapper();
+
+    public static void registerModule(Module module) {
+        MAPPER.registerModule(module);
+    }
+
+    public static String toJson(Object object) {
+        try {
+            return MAPPER.writeValueAsString(object);
+        } catch (JsonProcessingException e) {
+            throw new SerializeException("Failed to serialize object '%s'",
+                                         e, object);
+        }
+    }
+
+    public static <T> T fromJson(String json, Class<T> clazz) {
+        try {
+            return MAPPER.readValue(json, clazz);
+        } catch (IOException e) {
+            throw new SerializeException("Failed to deserialize json '%s'",
+                                         e, json);
+        }
+    }
+
+    public static <T> T convertValue(JsonNode node, Class<T> clazz) {
+        try {
+            return MAPPER.convertValue(node, clazz);
+        } catch (IllegalArgumentException e) {
+            throw new SerializeException("Failed to deserialize json node '%s'",
+                                         e, node);
+        }
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/util/SplicingIdGenerator.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/util/SplicingIdGenerator.java
new file mode 100644
index 0000000..732258d
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/util/SplicingIdGenerator.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.util;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Copied from HugeGraph(https://github.com/hugegraph/hugegraph)
+ */
+public class SplicingIdGenerator {
+
+    private static volatile SplicingIdGenerator instance;
+
+    public static SplicingIdGenerator instance() {
+        if (instance == null) {
+            synchronized (SplicingIdGenerator.class) {
+                if (instance == null) {
+                    instance = new SplicingIdGenerator();
+                }
+            }
+        }
+        return instance;
+    }
+
+    /*
+     * The following defines can't be java regex special characters:
+     * "\^$.|?*+()[{"
+     * See: http://www.regular-expressions.info/characters.html
+     */
+    private static final char ESCAPE = '`';
+    private static final char IDS_SPLITOR = '>';
+    private static final char ID_SPLITOR = ':';
+    private static final char NAME_SPLITOR = '!';
+
+    public static final String ESCAPE_STR = String.valueOf(ESCAPE);
+    public static final String IDS_SPLITOR_STR = String.valueOf(IDS_SPLITOR);
+    public static final String ID_SPLITOR_STR = String.valueOf(ID_SPLITOR);
+
+    /****************************** id generate ******************************/
+
+    /**
+     * Concat multiple ids into one composite id with IDS_SPLITOR
+     * @param ids the string id values to be concatted
+     * @return    concatted string value
+     */
+    public static String concat(String... ids) {
+        // NOTE: must support string id when using this method
+        return IdUtil.escape(IDS_SPLITOR, ESCAPE, ids);
+    }
+
+    /**
+     * Split a composite id into multiple ids with IDS_SPLITOR
+     * @param ids the string id value to be splitted
+     * @return    splitted string values
+     */
+    public static String[] split(String ids) {
+        return IdUtil.unescape(ids, IDS_SPLITOR_STR, ESCAPE_STR);
+    }
+
+    /**
+     * Concat property values with NAME_SPLITOR
+     * @param values the property values to be concatted
+     * @return       concatted string value
+     */
+    public static String concatValues(List<?> values) {
+        // Convert the object list to string array
+        int valuesSize = values.size();
+        String[] parts = new String[valuesSize];
+        for (int i = 0; i < valuesSize; i++) {
+            parts[i] = values.get(i).toString();
+        }
+        return IdUtil.escape(NAME_SPLITOR, ESCAPE, parts);
+    }
+
+    /**
+     * Concat property values with NAME_SPLITOR
+     * @param values the property values to be concatted
+     * @return       concatted string value
+     */
+    public static String concatValues(Object... values) {
+        return concatValues(Arrays.asList(values));
+    }
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/util/TaskCache.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/util/TaskCache.java
new file mode 100644
index 0000000..c278ac7
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/util/TaskCache.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.util;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import com.baidu.hugegraph.api.task.TaskAPI;
+import com.baidu.hugegraph.structure.Task;
+
+public class TaskCache {
+
+    private static final Task FAKE_TASK = new Task();
+
+    private Map<TaskAPI, Map<Long, Task>> taskTable;
+    private ScheduledExecutorService service;
+
+    private static TaskCache INSTANCE = new TaskCache();
+
+    private TaskCache() {
+        this.taskTable = new ConcurrentHashMap<>();
+        this.service = null;
+    }
+
+    public static TaskCache instance() {
+        return INSTANCE;
+    }
+
+    public Task get(TaskAPI api, long task) {
+        this.add(api, task);
+        return this.taskTable.get(api).get(task);
+    }
+
+    public void remove(TaskAPI api, long task) {
+        Map<Long, Task> tasks = this.tasks(api);
+        tasks.remove(task);
+        if (tasks.isEmpty()) {
+            this.taskTable.remove(api);
+        }
+        if (this.taskTable.isEmpty()) {
+            this.stop();
+        }
+    }
+
+    private void add(TaskAPI api, long task) {
+        Map<Long, Task> tasks = this.tasks(api);
+        if (!tasks.containsKey(task)) {
+            tasks.putIfAbsent(task, FAKE_TASK);
+        }
+        if (this.service == null || this.service.isShutdown()) {
+            this.start();
+        }
+    }
+
+    private Map<Long, Task> tasks(TaskAPI api) {
+        if (!this.taskTable.containsKey(api)) {
+            this.taskTable.putIfAbsent(api, new ConcurrentHashMap<>());
+        }
+        return this.taskTable.get(api);
+    }
+
+    private synchronized void start() {
+        if (this.service == null || this.service.isShutdown()) {
+            this.service = ExecutorUtil.newScheduledThreadPool("task-worker");
+            // Schedule a query task to query task status every 1 second,
+            this.service.scheduleAtFixedRate(this::asyncQueryTask, 0L, 1L,
+                                             TimeUnit.SECONDS);
+        }
+    }
+
+    private synchronized void stop() {
+        if (this.taskTable.isEmpty() && this.service != null) {
+            this.service.shutdown();
+        }
+    }
+
+    private void asyncQueryTask() {
+        for (Map.Entry<TaskAPI, Map<Long, Task>> query :
+             this.taskTable.entrySet()) {
+            TaskAPI api = query.getKey();
+            Map<Long, Task> targets = query.getValue();
+            if (targets == null || targets.isEmpty()) {
+                this.taskTable.remove(api);
+                continue;
+            }
+            List<Long> taskIds = new ArrayList<>(targets.keySet());
+            List<Task> results = api.list(taskIds);
+            for (Task task : results) {
+                targets.put(task.id(), task);
+            }
+        }
+    };
+}
diff --git a/hugegraph-client/src/main/java/com/baidu/hugegraph/version/ClientVersion.java b/hugegraph-client/src/main/java/com/baidu/hugegraph/version/ClientVersion.java
new file mode 100644
index 0000000..1ffbccb
--- /dev/null
+++ b/hugegraph-client/src/main/java/com/baidu/hugegraph/version/ClientVersion.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.version;
+
+import com.baidu.hugegraph.util.VersionUtil;
+import com.baidu.hugegraph.util.VersionUtil.Version;
+
+public class ClientVersion {
+
+    static {
+        // Check versions of the dependency packages
+        ClientVersion.check();
+    }
+
+    public static final String NAME = "hugegraph-client";
+
+    public static final Version VERSION = Version.of(ClientVersion.class);
+
+    public static void check() {
+        // Check version of hugegraph-common
+        VersionUtil.check(CommonVersion.VERSION, "2.1", "2.2",
+                          CommonVersion.NAME);
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/BaseClientTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/BaseClientTest.java
new file mode 100644
index 0000000..94d5c4d
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/BaseClientTest.java
@@ -0,0 +1,340 @@
+package com.baidu.hugegraph;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+
+import com.baidu.hugegraph.driver.AuthManager;
+import com.baidu.hugegraph.driver.GraphManager;
+import com.baidu.hugegraph.driver.GraphsManager;
+import com.baidu.hugegraph.driver.GremlinManager;
+import com.baidu.hugegraph.driver.HugeClient;
+import com.baidu.hugegraph.driver.MetricsManager;
+import com.baidu.hugegraph.driver.SchemaManager;
+import com.baidu.hugegraph.driver.TaskManager;
+import com.baidu.hugegraph.driver.TraverserManager;
+import com.baidu.hugegraph.structure.constant.T;
+import com.baidu.hugegraph.structure.graph.Edge;
+import com.baidu.hugegraph.structure.graph.Vertex;
+import com.baidu.hugegraph.structure.schema.EdgeLabel;
+import com.baidu.hugegraph.structure.schema.IndexLabel;
+import com.baidu.hugegraph.structure.schema.PropertyKey;
+import com.baidu.hugegraph.structure.schema.VertexLabel;
+import com.baidu.hugegraph.testutil.Assert;
+import com.baidu.hugegraph.testutil.Utils;
+import com.google.common.collect.ImmutableMap;
+
+public class BaseClientTest {
+
+    protected static final String BASE_URL = "http://127.0.0.1:8080";
+    protected static final String GRAPH = "hugegraph";
+    protected static final String USERNAME = "admin";
+    protected static final String PASSWORD = "pa";
+    protected static final int TIMEOUT = 10;
+
+    private static HugeClient client;
+
+    protected static HugeClient open() {
+        client = HugeClient.builder(BASE_URL, GRAPH)
+                           .configUser(USERNAME, PASSWORD)
+                           .build();
+        return client;
+    }
+
+    @BeforeClass
+    public static void init() {
+        if (client == null) {
+            open();
+        }
+    }
+
+    @AfterClass
+    public static void clear() throws Exception {
+        // client.close();
+    }
+
+    public static HugeClient baseClient() {
+        return client;
+    }
+
+    public static SchemaManager schema() {
+        Assert.assertNotNull("Not opened client", client);
+        return client.schema();
+    }
+
+    public static GraphManager graph() {
+        Assert.assertNotNull("Not opened client", client);
+        return client.graph();
+    }
+
+    public static GremlinManager gremlin() {
+        Assert.assertNotNull("Not opened client", client);
+        return client.gremlin();
+    }
+
+    public static TraverserManager traverser() {
+        Assert.assertNotNull("Not opened client", client);
+        return client.traverser();
+    }
+
+    public static TaskManager task() {
+        Assert.assertNotNull("Not opened client", client);
+        return client.task();
+    }
+
+    public static AuthManager auth() {
+        Assert.assertNotNull("Not opened client", client);
+        return client.auth();
+    }
+
+    public static GraphsManager graphs() {
+        Assert.assertNotNull("Not opened client", client);
+        return client.graphs();
+    }
+
+    public static MetricsManager metrics() {
+        Assert.assertNotNull("Not opened client", client);
+        return client.metrics();
+    }
+
+    @Before
+    public void setup() {
+        // this.clearData();
+    }
+
+    @After
+    public void teardown() throws Exception {
+        // pass
+    }
+
+    protected static Object getVertexId(String label, String key,
+                                        String value) {
+        return getVertex(label, key, value).id();
+    }
+
+    protected static Vertex getVertex(String label, String key, String value) {
+        Map<String, Object> params = ImmutableMap.of(key, value);
+        List<Vertex> vertices = graph().listVertices(label, params);
+        Assert.assertEquals(1, vertices.size());
+        return vertices.get(0);
+    }
+
+    protected static String getEdgeId(String label, String key, String value) {
+        return getEdge(label, key, value).id();
+    }
+
+    protected static Edge getEdge(String label, String key, String value) {
+        Map<String, Object> params = ImmutableMap.of(key, value);
+        List<Edge> edges = graph().listEdges(label, params);
+        Assert.assertEquals(1, edges.size());
+        return edges.get(0);
+    }
+
+    protected static void assertContains(List<PropertyKey> propertyKeys,
+                                         PropertyKey propertyKey) {
+        Assert.assertTrue(Utils.contains(propertyKeys, propertyKey));
+    }
+
+    protected static void assertContains(List<VertexLabel> vertexLabels,
+                                         VertexLabel vertexLabel) {
+        Assert.assertTrue(Utils.contains(vertexLabels, vertexLabel));
+    }
+
+    protected static void assertContains(List<EdgeLabel> edgeLabels,
+                                         EdgeLabel edgeLabel) {
+        Assert.assertTrue(Utils.contains(edgeLabels, edgeLabel));
+    }
+
+    protected static void assertContains(List<IndexLabel> indexLabels,
+                                         IndexLabel indexLabel) {
+        Assert.assertTrue(Utils.contains(indexLabels, indexLabel));
+    }
+
+    protected static void initPropertyKey() {
+        SchemaManager schema = schema();
+        schema.propertyKey("name").asText().ifNotExist().create();
+        schema.propertyKey("age").asInt().ifNotExist().create();
+        schema.propertyKey("city").asText().ifNotExist().create();
+        schema.propertyKey("lang").asText().ifNotExist().create();
+        schema.propertyKey("date").asDate().ifNotExist().create();
+        schema.propertyKey("price").asInt().ifNotExist().create();
+        schema.propertyKey("weight").asDouble().ifNotExist().create();
+    }
+
+    protected static void initVertexLabel() {
+        SchemaManager schema = schema();
+
+        schema.vertexLabel("person")
+              .properties("name", "age", "city")
+              .primaryKeys("name")
+              .nullableKeys("city")
+              .ifNotExist()
+              .create();
+
+        schema.vertexLabel("software")
+              .properties("name", "lang", "price")
+              .primaryKeys("name")
+              .nullableKeys("price")
+              .ifNotExist()
+              .create();
+
+        schema.vertexLabel("book")
+              .useCustomizeStringId()
+              .properties("name", "price")
+              .nullableKeys("price")
+              .ifNotExist()
+              .create();
+    }
+
+    protected static void initEdgeLabel() {
+        SchemaManager schema = schema();
+
+        schema.edgeLabel("knows")
+              .sourceLabel("person")
+              .targetLabel("person")
+              .multiTimes()
+              .properties("date", "city")
+              .sortKeys("date")
+              .nullableKeys("city")
+              .ifNotExist()
+              .create();
+
+        schema.edgeLabel("created")
+              .sourceLabel("person")
+              .targetLabel("software")
+              .properties("date", "city")
+              .nullableKeys("city")
+              .ifNotExist()
+              .create();
+    }
+
+    protected static void initIndexLabel() {
+        SchemaManager schema = schema();
+
+        schema.indexLabel("personByCity")
+              .onV("person")
+              .by("city")
+              .secondary()
+              .ifNotExist()
+              .create();
+
+        schema.indexLabel("personByAge")
+              .onV("person")
+              .by("age")
+              .range()
+              .ifNotExist()
+              .create();
+
+        schema.indexLabel("knowsByDate")
+              .onE("knows")
+              .by("date")
+              .secondary()
+              .ifNotExist()
+              .create();
+
+        schema.indexLabel("createdByDate")
+              .onE("created")
+              .by("date")
+              .secondary()
+              .ifNotExist()
+              .create();
+    }
+
+    protected static void initVertex() {
+        graph().addVertex(T.label, "person", "name", "marko",
+                          "age", 29, "city", "Beijing");
+        graph().addVertex(T.label, "person", "name", "vadas",
+                          "age", 27, "city", "Hongkong");
+        graph().addVertex(T.label, "software", "name", "lop",
+                          "lang", "java", "price", 328);
+        graph().addVertex(T.label, "person", "name", "josh",
+                          "age", 32, "city", "Beijing");
+        graph().addVertex(T.label, "software", "name", "ripple",
+                          "lang", "java", "price", 199);
+        graph().addVertex(T.label, "person", "name", "peter",
+                          "age", 29, "city", "Shanghai");
+    }
+
+    protected static void initEdge() {
+        Object markoId = getVertexId("person", "name", "marko");
+        Object vadasId = getVertexId("person", "name", "vadas");
+        Object joshId = getVertexId("person", "name", "josh");
+        Object peterId = getVertexId("person", "name", "peter");
+        Object lopId = getVertexId("software", "name", "lop");
+        Object rippleId = getVertexId("software", "name", "ripple");
+
+        graph().addEdge(markoId, "knows", vadasId, "date", "2012-01-10");
+        graph().addEdge(markoId, "knows", joshId, "date", "2013-01-10");
+        graph().addEdge(markoId, "created", lopId,
+                        "date", "2014-01-10", "city", "Shanghai");
+        graph().addEdge(joshId, "created", rippleId,
+                        "date", "2015-01-10", "city", "Beijing");
+        graph().addEdge(joshId, "created", lopId,
+                        "date", "2016-01-10", "city", "Beijing");
+        graph().addEdge(peterId, "created", lopId,
+                        "date", "2017-01-10", "city", "Hongkong");
+    }
+
+    protected List<Vertex> create100PersonBatch() {
+        List<Vertex> vertices = new ArrayList<>(100);
+        for (int i = 0; i < 100; i++) {
+            Vertex vertex = new Vertex("person");
+            vertex.property("name", "Person" + "-" + i);
+            vertex.property("city", "Beijing");
+            vertex.property("age", 30);
+            vertices.add(vertex);
+        }
+        return vertices;
+    }
+
+    protected List<Vertex> create50SoftwareBatch() {
+        List<Vertex> vertices = new ArrayList<>(50);
+        for (int i = 0; i < 50; i++) {
+            Vertex vertex = new Vertex("software");
+            vertex.property("name", "Software" + "-" + i);
+            vertex.property("lang", "java");
+            vertex.property("price", 328);
+            vertices.add(vertex);
+        }
+        return vertices;
+    }
+
+    protected List<Edge> create50CreatedBatch() {
+        VertexLabel person = schema().getVertexLabel("person");
+        VertexLabel software = schema().getVertexLabel("software");
+
+        List<Edge> edges = new ArrayList<>(50);
+        for (int i = 0; i < 50; i++) {
+            Edge edge = new Edge("created");
+            edge.sourceLabel("person");
+            edge.targetLabel("software");
+            edge.sourceId(person.id() + ":Person-" + i);
+            edge.targetId(software.id() + ":Software-" + i);
+            edge.property("date", "2017-03-24");
+            edge.property("city", "Hongkong");
+            edges.add(edge);
+        }
+        return edges;
+    }
+
+    protected List<Edge> create50KnowsBatch() {
+        VertexLabel person = schema().getVertexLabel("person");
+
+        List<Edge> edges = new ArrayList<>(50);
+        for (int i = 0; i < 50; i++) {
+            Edge edge = new Edge("knows");
+            edge.sourceLabel("person");
+            edge.targetLabel("person");
+            edge.sourceId(person.id() + ":Person-" + i);
+            edge.targetId(person.id() + ":Person-" + (i + 50));
+            edge.property("date", "2017-03-24");
+            edges.add(edge);
+        }
+        return edges;
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/api/ApiTestSuite.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/ApiTestSuite.java
new file mode 100644
index 0000000..62329db
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/ApiTestSuite.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+import com.baidu.hugegraph.api.auth.AccessApiTest;
+import com.baidu.hugegraph.api.auth.BelongApiTest;
+import com.baidu.hugegraph.api.auth.GroupApiTest;
+import com.baidu.hugegraph.api.auth.LoginApiTest;
+import com.baidu.hugegraph.api.auth.LogoutApiTest;
+import com.baidu.hugegraph.api.auth.ProjectApiTest;
+import com.baidu.hugegraph.api.auth.TargetApiTest;
+import com.baidu.hugegraph.api.auth.TokenApiTest;
+import com.baidu.hugegraph.api.auth.UserApiTest;
+import com.baidu.hugegraph.api.traverser.AllShortestPathsApiTest;
+import com.baidu.hugegraph.api.traverser.CommonTraverserApiTest;
+import com.baidu.hugegraph.api.traverser.CountApiTest;
+import com.baidu.hugegraph.api.traverser.CustomizedPathsApiTest;
+import com.baidu.hugegraph.api.traverser.FusiformSimilarityApiTest;
+import com.baidu.hugegraph.api.traverser.JaccardSimilarityApiTest;
+import com.baidu.hugegraph.api.traverser.KneighborApiTest;
+import com.baidu.hugegraph.api.traverser.KoutApiTest;
+import com.baidu.hugegraph.api.traverser.MultiNodeShortestPathApiTest;
+import com.baidu.hugegraph.api.traverser.NeighborRankApiTest;
+import com.baidu.hugegraph.api.traverser.PathsApiTest;
+import com.baidu.hugegraph.api.traverser.PersonalRankApiTest;
+import com.baidu.hugegraph.api.traverser.RingsRaysApiTest;
+import com.baidu.hugegraph.api.traverser.SameNeighborsApiTest;
+import com.baidu.hugegraph.api.traverser.ShortestPathApiTest;
+import com.baidu.hugegraph.api.traverser.SingleSourceShortestPathApiTest;
+import com.baidu.hugegraph.api.traverser.TemplatePathsApiTest;
+import com.baidu.hugegraph.api.traverser.WeightedShortestPathApiTest;
+
+@RunWith(Suite.class)
+@Suite.SuiteClasses({
+    PropertyKeyApiTest.class,
+    VertexLabelApiTest.class,
+    EdgeLabelApiTest.class,
+    IndexLabelApiTest.class,
+    SchemaApiTest.class,
+
+    VertexApiTest.class,
+    EdgeApiTest.class,
+    BatchUpdateElementApiTest.class,
+
+    GremlinApiTest.class,
+    VariablesApiTest.class,
+    TaskApiTest.class,
+    JobApiTest.class,
+    RestoreApiTest.class,
+    GraphsApiTest.class,
+
+    CommonTraverserApiTest.class,
+    KoutApiTest.class,
+    KneighborApiTest.class,
+    PathsApiTest.class,
+    CountApiTest.class,
+    RingsRaysApiTest.class,
+    SameNeighborsApiTest.class,
+    JaccardSimilarityApiTest.class,
+    ShortestPathApiTest.class,
+    AllShortestPathsApiTest.class,
+    SingleSourceShortestPathApiTest.class,
+    WeightedShortestPathApiTest.class,
+    MultiNodeShortestPathApiTest.class,
+    CustomizedPathsApiTest.class,
+    TemplatePathsApiTest.class,
+    FusiformSimilarityApiTest.class,
+    NeighborRankApiTest.class,
+    PersonalRankApiTest.class,
+
+    TargetApiTest.class,
+    GroupApiTest.class,
+    UserApiTest.class,
+    AccessApiTest.class,
+    BelongApiTest.class,
+    ProjectApiTest.class,
+    LoginApiTest.class,
+    LogoutApiTest.class,
+    TokenApiTest.class
+})
+public class ApiTestSuite {
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/api/BaseApiTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/BaseApiTest.java
new file mode 100644
index 0000000..d8a5d1f
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/BaseApiTest.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+
+import com.baidu.hugegraph.BaseClientTest;
+import com.baidu.hugegraph.api.graph.EdgeAPI;
+import com.baidu.hugegraph.api.graph.VertexAPI;
+import com.baidu.hugegraph.api.graphs.GraphsAPI;
+import com.baidu.hugegraph.api.job.RebuildAPI;
+import com.baidu.hugegraph.api.schema.EdgeLabelAPI;
+import com.baidu.hugegraph.api.schema.IndexLabelAPI;
+import com.baidu.hugegraph.api.schema.PropertyKeyAPI;
+import com.baidu.hugegraph.api.schema.SchemaAPI;
+import com.baidu.hugegraph.api.schema.VertexLabelAPI;
+import com.baidu.hugegraph.api.task.TaskAPI;
+import com.baidu.hugegraph.api.variables.VariablesAPI;
+import com.baidu.hugegraph.api.version.VersionAPI;
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.testutil.Assert;
+import com.baidu.hugegraph.util.VersionUtil;
+
+public class BaseApiTest extends BaseClientTest {
+
+    protected static RestClient client;
+
+    protected static VersionAPI versionAPI;
+    protected static GraphsAPI graphsAPI;
+
+    protected static PropertyKeyAPI propertyKeyAPI;
+    protected static VertexLabelAPI vertexLabelAPI;
+    protected static EdgeLabelAPI edgeLabelAPI;
+    protected static IndexLabelAPI indexLabelAPI;
+    protected static SchemaAPI schemaAPI;
+
+    protected static VertexAPI vertexAPI;
+    protected static EdgeAPI edgeAPI;
+
+    protected static VariablesAPI variablesAPI;
+    protected static TaskAPI taskAPI;
+    protected static RebuildAPI rebuildAPI;
+
+    protected static RestClient initClient() {
+        client = new RestClient(BASE_URL, USERNAME, PASSWORD, TIMEOUT);
+        return client;
+    }
+
+    @BeforeClass
+    public static void init() {
+        BaseClientTest.init();
+        if (client == null) {
+            initClient();
+        }
+
+        versionAPI = new VersionAPI(client);
+        client.apiVersion(VersionUtil.Version.of(versionAPI.get().get("api")));
+
+        graphsAPI = new GraphsAPI(client);
+
+        propertyKeyAPI = new PropertyKeyAPI(client, GRAPH);
+        vertexLabelAPI = new VertexLabelAPI(client, GRAPH);
+        edgeLabelAPI = new EdgeLabelAPI(client, GRAPH);
+        indexLabelAPI = new IndexLabelAPI(client, GRAPH);
+        schemaAPI = new SchemaAPI(client, GRAPH);
+
+        vertexAPI = new VertexAPI(client, GRAPH);
+        edgeAPI = new EdgeAPI(client, GRAPH);
+
+        variablesAPI = new VariablesAPI(client, GRAPH);
+        taskAPI = new TaskAPI(client, GRAPH);
+        rebuildAPI = new RebuildAPI(client, GRAPH);
+    }
+
+    @AfterClass
+    public static void clear() throws Exception {
+        Assert.assertNotNull("Not opened client", client);
+
+        clearData();
+        client.close();
+        client = null;
+
+        BaseClientTest.clear();
+    }
+
+    protected RestClient client() {
+        return client;
+    }
+
+    protected static void clearData() {
+        // Clear edge
+        edgeAPI.list(-1).results().forEach(edge -> {
+            edgeAPI.delete(edge.id());
+        });
+        // Clear vertex
+        vertexAPI.list(-1).results().forEach(vertex -> {
+            vertexAPI.delete(vertex.id());
+        });
+
+        // Clear schema
+        List<Long> ilTaskIds = new ArrayList<>();
+        indexLabelAPI.list().forEach(indexLabel -> {
+            ilTaskIds.add(indexLabelAPI.delete(indexLabel.name()));
+        });
+        ilTaskIds.forEach(taskId -> waitUntilTaskCompleted(taskId));
+
+        List<Long> elTaskIds = new ArrayList<>();
+        edgeLabelAPI.list().forEach(edgeLabel -> {
+            elTaskIds.add(edgeLabelAPI.delete(edgeLabel.name()));
+        });
+        elTaskIds.forEach(taskId -> waitUntilTaskCompleted(taskId));
+
+        List<Long> vlTaskIds = new ArrayList<>();
+        vertexLabelAPI.list().forEach(vertexLabel -> {
+            vlTaskIds.add(vertexLabelAPI.delete(vertexLabel.name()));
+        });
+        vlTaskIds.forEach(taskId -> waitUntilTaskCompleted(taskId));
+
+        List<Long> pkTaskIds = new ArrayList<>();
+        propertyKeyAPI.list().forEach(propertyKey -> {
+            pkTaskIds.add(propertyKeyAPI.delete(propertyKey.name()));
+        });
+        pkTaskIds.forEach(taskId -> waitUntilTaskCompleted(taskId));
+
+        // Clear system
+        taskAPI.list(null, -1).forEach(task -> {
+            taskAPI.delete(task.id());
+        });
+    }
+
+    protected static void waitUntilTaskCompleted(long taskId) {
+        if (taskId == 0L) {
+            return;
+        }
+        taskAPI.waitUntilTaskSuccess(taskId, TIMEOUT);
+    }
+
+    protected static void waitUntilTaskCompleted(long taskId, long timeout) {
+        if (taskId == 0L) {
+            return;
+        }
+        taskAPI.waitUntilTaskSuccess(taskId, timeout);
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/api/BatchUpdateElementApiTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/BatchUpdateElementApiTest.java
new file mode 100644
index 0000000..d4ec65f
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/BatchUpdateElementApiTest.java
@@ -0,0 +1,760 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api;
+
+import static com.baidu.hugegraph.structure.graph.UpdateStrategy.INTERSECTION;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.After;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.baidu.hugegraph.driver.SchemaManager;
+import com.baidu.hugegraph.exception.ServerException;
+import com.baidu.hugegraph.structure.GraphElement;
+import com.baidu.hugegraph.structure.graph.BatchEdgeRequest;
+import com.baidu.hugegraph.structure.graph.BatchVertexRequest;
+import com.baidu.hugegraph.structure.graph.Edge;
+import com.baidu.hugegraph.structure.graph.UpdateStrategy;
+import com.baidu.hugegraph.structure.graph.Vertex;
+import com.baidu.hugegraph.structure.schema.VertexLabel;
+import com.baidu.hugegraph.testutil.Assert;
+import com.baidu.hugegraph.testutil.Whitebox;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+public class BatchUpdateElementApiTest extends BaseApiTest {
+
+    private static final int BATCH_SIZE = 5;
+
+    @BeforeClass
+    public static void prepareSchema() {
+        SchemaManager schema = schema();
+        schema.propertyKey("name").asText().ifNotExist().create();
+        schema.propertyKey("price").asInt().ifNotExist().create();
+        schema.propertyKey("date").asDate().ifNotExist().create();
+        schema.propertyKey("set").asText().valueSet().ifNotExist().create();
+        schema.propertyKey("list").asText().valueList().ifNotExist().create();
+
+        schema.vertexLabel("object")
+              .properties("name", "price", "date", "set", "list")
+              .primaryKeys("name")
+              .nullableKeys("price", "date", "set", "list")
+              .ifNotExist()
+              .create();
+
+        schema.edgeLabel("updates")
+              .sourceLabel("object")
+              .targetLabel("object")
+              .properties("name", "price", "date", "set", "list")
+              .nullableKeys("name", "price", "date", "set", "list")
+              .ifNotExist()
+              .create();
+    }
+
+    @Override
+    @After
+    public void teardown() {
+        vertexAPI.list(-1).results().forEach(v -> vertexAPI.delete(v.id()));
+        edgeAPI.list(-1).results().forEach(e -> edgeAPI.delete(e.id()));
+    }
+
+    /* Vertex Test */
+    @Test
+    public void testVertexBatchUpdateStrategySum() {
+        BatchVertexRequest req = batchVertexRequest("price", 1, -1,
+                                                    UpdateStrategy.SUM);
+        List<Vertex> vertices = vertexAPI.update(req);
+        assertBatchResponse(vertices, "price", 0);
+
+        req = batchVertexRequest("price", 2, 3, UpdateStrategy.SUM);
+        vertices = vertexAPI.update(req);
+        assertBatchResponse(vertices, "price", 5);
+    }
+
+    @Test
+    public void testVertexBatchUpdateStrategyBigger() {
+        // TODO: Add date comparison after fixing the date serialization bug
+        BatchVertexRequest req = batchVertexRequest("price", -3, 1,
+                                                    UpdateStrategy.BIGGER);
+
+        List<Vertex> vertices = vertexAPI.update(req);
+        assertBatchResponse(vertices, "price", 1);
+
+        req = batchVertexRequest("price", 7, 3, UpdateStrategy.BIGGER);
+        vertices = vertexAPI.update(req);
+        assertBatchResponse(vertices, "price", 7);
+    }
+
+    @Test
+    public void testVertexBatchUpdateStrategySmaller() {
+        BatchVertexRequest req = batchVertexRequest("price", -3, 1,
+                                                    UpdateStrategy.SMALLER);
+
+        List<Vertex> vertices = vertexAPI.update(req);
+        assertBatchResponse(vertices, "price", -3);
+
+        req = batchVertexRequest("price", 7, 3, UpdateStrategy.SMALLER);
+        vertices = vertexAPI.update(req);
+        assertBatchResponse(vertices, "price", 3);
+    }
+
+    @Test
+    public void testVertexBatchUpdateStrategyUnion() {
+        BatchVertexRequest req = batchVertexRequest("set", "old", "new",
+                                                    UpdateStrategy.UNION);
+
+        List<Vertex> vertices = vertexAPI.update(req);
+        assertBatchResponse(vertices, "set", "new", "old");
+
+        req = batchVertexRequest("set", "old", "old", UpdateStrategy.UNION);
+        vertices = vertexAPI.update(req);
+        assertBatchResponse(vertices, "set", "old");
+    }
+
+    @Test
+    public void testVertexBatchUpdateStrategyIntersection() {
+        BatchVertexRequest req = batchVertexRequest("set", "old", "new",
+                                                    INTERSECTION);
+
+        List<Vertex> vertices = vertexAPI.update(req);
+        assertBatchResponse(vertices, "set");
+
+        req = batchVertexRequest("set", "old", "old", INTERSECTION);
+        vertices = vertexAPI.update(req);
+        assertBatchResponse(vertices, "set", "old");
+    }
+
+    @Test
+    public void testVertexBatchUpdateStrategyAppend() {
+        BatchVertexRequest req = batchVertexRequest("list", "old", "old",
+                                                    UpdateStrategy.APPEND);
+
+        List<Vertex> vertices = vertexAPI.update(req);
+        assertBatchResponse(vertices, "list", "old", "old");
+
+        req = batchVertexRequest("list", "old", "new", UpdateStrategy.APPEND);
+        vertices = vertexAPI.update(req);
+        assertBatchResponse(vertices, "list", "old", "new");
+    }
+
+    @Test
+    public void testVertexBatchUpdateStrategyEliminate() {
+        BatchVertexRequest req = batchVertexRequest("list", "old", "old",
+                                                    UpdateStrategy.ELIMINATE);
+
+        List<Vertex> vertices = vertexAPI.update(req);
+        assertBatchResponse(vertices, "list");
+
+        req = batchVertexRequest("list", "old", "x", UpdateStrategy.ELIMINATE);
+        vertices = vertexAPI.update(req);
+        assertBatchResponse(vertices, "list", "old");
+    }
+
+    @Test
+    public void testVertexBatchUpdateStrategyOverride() {
+        BatchVertexRequest req = batchVertexRequest("price", -1, 1,
+                                                    UpdateStrategy.OVERRIDE);
+        assertBatchResponse(vertexAPI.update(req), "price", 1);
+
+        // Construct a specialized test case
+        graph().addVertices(this.createNVertexBatch("object", -1, 2));
+        List<String> list = ImmutableList.of("newStr1", "newStr2");
+
+        Vertex v1 = new Vertex("object");
+        v1.property("name", "tom");
+        v1.property("price", 1);
+        v1.property("list", list);
+
+        Vertex v2 = new Vertex("object");
+        v2.property("name", "tom");
+
+        Map<String, UpdateStrategy> strategies;
+        strategies = ImmutableMap.of("price", UpdateStrategy.OVERRIDE,
+                                     "list", UpdateStrategy.OVERRIDE);
+        req = BatchVertexRequest.createBuilder()
+                                .vertices(ImmutableList.of(v1, v2))
+                                .updatingStrategies(strategies)
+                                .createIfNotExist(true)
+                                .build();
+
+        List<Vertex> vertices = vertexAPI.update(req);
+        Assert.assertEquals(1, vertices.size());
+        Map<String, Object> expectProperties = ImmutableMap.of("name", "tom",
+                                                               "price", 1,
+                                                               "list", list);
+        Assert.assertEquals(vertices.get(0).properties(), expectProperties);
+    }
+
+    @Test
+    public void testVertexBatchUpdateWithNullValues() {
+        BatchVertexRequest req = batchVertexRequest("price", 1, null,
+                                                    UpdateStrategy.OVERRIDE);
+        List<Vertex> vertices = vertexAPI.update(req);
+        assertBatchResponse(vertices, "price", 1);
+    }
+
+    @Test
+    public void testVertexBatchUpdateWithInvalidArgs() {
+        BatchVertexRequest req1 = batchVertexRequest("set", "old", "old",
+                                                     UpdateStrategy.UNION);
+
+        Assert.assertThrows(ServerException.class, () -> {
+            List<Vertex> vertices = Whitebox.getInternalState(req1, "vertices");
+            vertices.set(1, null);
+            Whitebox.setInternalState(req1, "vertices", vertices);
+            vertexAPI.update(req1);
+        }, e -> {
+            Assert.assertContains("The batch body can't contain null record",
+                                  e.toString());
+        });
+
+        BatchVertexRequest req2 = batchVertexRequest("list", "old", "old",
+                                                     UpdateStrategy.ELIMINATE);
+        Assert.assertThrows(ServerException.class, () -> {
+            Whitebox.setInternalState(req2, "vertices", null);
+            vertexAPI.update(req2);
+        }, e -> {
+            String expect = "Parameter 'vertices' can't be null";
+            Assert.assertContains(expect, e.getMessage());
+        });
+
+        BatchVertexRequest req3 = batchVertexRequest("list", "old", "old",
+                                                     UpdateStrategy.ELIMINATE);
+        Assert.assertThrows(ServerException.class, () -> {
+            Whitebox.setInternalState(req3, "vertices", ImmutableList.of());
+            vertexAPI.update(req3);
+        }, e -> {
+            String expect = "The number of vertices can't be 0";
+            Assert.assertContains(expect, e.getMessage());
+        });
+
+        BatchVertexRequest req4 = batchVertexRequest("list", "old", "old",
+                                                     UpdateStrategy.ELIMINATE);
+        Assert.assertThrows(ServerException.class, () -> {
+            Whitebox.setInternalState(req4, "createIfNotExist", false);
+            vertexAPI.update(req4);
+        }, e -> {
+            String expect = "Parameter 'create_if_not_exist' " +
+                            "dose not support false now";
+            Assert.assertContains(expect, e.getMessage());
+        });
+
+        BatchVertexRequest req5 = batchVertexRequest("list", "old", "old",
+                                                     UpdateStrategy.ELIMINATE);
+        Assert.assertThrows(ServerException.class, () -> {
+            Whitebox.setInternalState(req5, "updateStrategies", null);
+            vertexAPI.update(req5);
+        }, e -> {
+            String expect = "Parameter 'update_strategies' can't be empty";
+            Assert.assertContains(expect, e.getMessage());
+        });
+
+        BatchVertexRequest req6 = batchVertexRequest("list", "old", "old",
+                                                     UpdateStrategy.ELIMINATE);
+        Assert.assertThrows(ServerException.class, () -> {
+            Whitebox.setInternalState(req6, "updateStrategies",
+                                      ImmutableMap.of());
+            vertexAPI.update(req6);
+        }, e -> {
+            String expect = "Parameter 'update_strategies' can't be empty";
+            Assert.assertContains(expect, e.getMessage());
+        });
+    }
+
+    @Test
+    public void testVertexInvalidUpdateStrategy() {
+        BatchVertexRequest req1 = batchVertexRequest("name", "old", "new",
+                                                     UpdateStrategy.SUM);
+        Assert.assertThrows(ServerException.class, () -> {
+            vertexAPI.update(req1);
+        }, e -> {
+            String expect = "Property type must be Number for strategy SUM, " +
+                            "but got type String, String";
+            Assert.assertContains(expect, e.getMessage());
+        });
+
+        BatchVertexRequest req2 = batchVertexRequest("name", "old", "new",
+                                                     UpdateStrategy.BIGGER);
+        Assert.assertThrows(ServerException.class, () -> {
+            vertexAPI.update(req2);
+        }, e -> {
+            String expect = "Property type must be Date or Number " +
+                            "for strategy BIGGER, but got type String, String";
+            Assert.assertContains(expect, e.getMessage());
+        });
+
+        BatchVertexRequest req3 = batchVertexRequest("name", "old", "new",
+                                                     UpdateStrategy.SMALLER);
+        Assert.assertThrows(ServerException.class, () -> {
+            vertexAPI.update(req3);
+        }, e -> {
+            String expect = "Property type must be Date or Number " +
+                            "for strategy SMALLER, but got type String, String";
+            Assert.assertContains(expect, e.getMessage());
+        });
+
+        BatchVertexRequest req4 = batchVertexRequest("price", 1, -1,
+                                                     UpdateStrategy.UNION);
+        Assert.assertThrows(ServerException.class, () -> {
+            vertexAPI.update(req4);
+        }, e -> {
+            String expect = "Property type must be Set or List " +
+                            "for strategy UNION, but got type Integer, Integer";
+            Assert.assertContains(expect, e.getMessage());
+        });
+
+        BatchVertexRequest req5 = batchVertexRequest("date", "old", "new",
+                                                     INTERSECTION);
+        Assert.assertThrows(ServerException.class, () -> {
+            vertexAPI.update(req5);
+        }, e -> {
+            String expect = "Property type must be Set or List for " +
+                            "strategy INTERSECTION, but got type Date, Long";
+            Assert.assertContains(expect, e.getMessage());
+        });
+
+        BatchVertexRequest req6 = batchVertexRequest("price", 1, -1,
+                                                     UpdateStrategy.APPEND);
+        Assert.assertThrows(ServerException.class, () -> {
+            vertexAPI.update(req6);
+        }, e -> {
+            String expect = "Property type must be Set or List for " +
+                            "strategy APPEND, but got type Integer, Integer";
+            Assert.assertContains(expect, e.getMessage());
+        });
+
+        BatchVertexRequest req7 = batchVertexRequest("name", "old", "new",
+                                                     UpdateStrategy.ELIMINATE);
+        Assert.assertThrows(ServerException.class, () -> {
+            vertexAPI.update(req7);
+        }, e -> {
+            String expect = "Property type must be Set or List for " +
+                            "strategy ELIMINATE, but got type String, String";
+            Assert.assertContains(expect, e.getMessage());
+        });
+    }
+
+    /* Edge Test */
+    @Test
+    public void testEdgeBatchUpdateStrategySum() {
+        BatchEdgeRequest req = batchEdgeRequest("price", -1, 1,
+                                                UpdateStrategy.SUM);
+        List<Edge> edges = edgeAPI.update(req);
+        assertBatchResponse(edges, "price", 0);
+
+        req = batchEdgeRequest("price", 2, 3, UpdateStrategy.SUM);
+        edges = edgeAPI.update(req);
+        assertBatchResponse(edges, "price", 5);
+    }
+
+    @Test
+    public void testEdgeBatchUpdateStrategyBigger() {
+        // TODO: Add date comparison after fixing the date serialization bug
+        BatchEdgeRequest req = batchEdgeRequest("price", -3, 1,
+                                                UpdateStrategy.BIGGER);
+        List<Edge> edges = edgeAPI.update(req);
+        assertBatchResponse(edges, "price", 1);
+
+        req = batchEdgeRequest("price", 7, 3, UpdateStrategy.BIGGER);
+        edges = edgeAPI.update(req);
+        assertBatchResponse(edges, "price", 7);
+    }
+
+    @Test
+    public void testEdgeBatchUpdateStrategySmaller() {
+        BatchEdgeRequest req = batchEdgeRequest("price", -3, 1,
+                                                UpdateStrategy.SMALLER);
+        List<Edge> edges = edgeAPI.update(req);
+        assertBatchResponse(edges, "price", -3);
+
+        req = batchEdgeRequest("price", 7, 3, UpdateStrategy.SMALLER);
+        edges = edgeAPI.update(req);
+        assertBatchResponse(edges, "price", 3);
+    }
+
+    @Test
+    public void testEdgeBatchUpdateStrategyUnion() {
+        BatchEdgeRequest req = batchEdgeRequest("set", "old", "new",
+                                                UpdateStrategy.UNION);
+        List<Edge> edges = edgeAPI.update(req);
+        assertBatchResponse(edges, "set", "new", "old");
+
+        req = batchEdgeRequest("set", "old", "old", UpdateStrategy.UNION);
+        edges = edgeAPI.update(req);
+        assertBatchResponse(edges, "set", "old");
+    }
+
+    @Test
+    public void testEdgeBatchUpdateStrategyIntersection() {
+        BatchEdgeRequest req = batchEdgeRequest("set", "old", "new",
+                                                INTERSECTION);
+        List<Edge> edges = edgeAPI.update(req);
+        assertBatchResponse(edges, "set");
+
+        req = batchEdgeRequest("set", "old", "old", INTERSECTION);
+        edges = edgeAPI.update(req);
+        assertBatchResponse(edges, "set", "old");
+    }
+
+    @Test
+    public void testEdgeBatchUpdateStrategyAppend() {
+        BatchEdgeRequest req = batchEdgeRequest("list", "old", "old",
+                                                UpdateStrategy.APPEND);
+        List<Edge> edges = edgeAPI.update(req);
+        assertBatchResponse(edges, "list", "old", "old");
+
+        req = batchEdgeRequest("list", "old", "new", UpdateStrategy.APPEND);
+        edges = edgeAPI.update(req);
+        assertBatchResponse(edges, "list", "old", "new");
+    }
+
+    @Test
+    public void testEdgeBatchUpdateStrategyEliminate() {
+        BatchEdgeRequest req = batchEdgeRequest("list", "old", "old",
+                                                UpdateStrategy.ELIMINATE);
+        List<Edge> edges = edgeAPI.update(req);
+        assertBatchResponse(edges, "list");
+
+        req = batchEdgeRequest("list", "old", "new", UpdateStrategy.ELIMINATE);
+        edges = edgeAPI.update(req);
+        assertBatchResponse(edges, "list", "old");
+    }
+
+    @Test
+    public void testEdgeBatchUpdateStrategyOverride() {
+        BatchEdgeRequest req = batchEdgeRequest("price", -1, 1,
+                                                UpdateStrategy.OVERRIDE);
+        assertBatchResponse(edgeAPI.update(req), "price", 1);
+
+        // Construct a specialized test case
+        graph().addEdges(this.createNEdgesBatch("object", "updates", -1, 2));
+        List<String> list = ImmutableList.of("newStr1", "newStr2");
+        String vid = "1:a";
+
+        Edge e1 = new Edge("updates");
+        e1.sourceLabel("object");
+        e1.targetLabel("object");
+        e1.sourceId(vid);
+        e1.targetId(vid);
+        e1.property("name", "tom");
+        e1.property("price", 1);
+        e1.property("list", list);
+
+        Edge e2 = new Edge("updates");
+        e2.sourceLabel("object");
+        e2.targetLabel("object");
+        e2.sourceId(vid);
+        e2.targetId(vid);
+        e2.property("name", "tom");
+
+        Map<String, UpdateStrategy> strategies;
+        strategies = ImmutableMap.of("price", UpdateStrategy.OVERRIDE,
+                                     "list", UpdateStrategy.OVERRIDE);
+        req = BatchEdgeRequest.createBuilder()
+                              .edges(ImmutableList.of(e1, e2))
+                              .updatingStrategies(strategies)
+                              .checkVertex(false)
+                              .createIfNotExist(true)
+                              .build();
+
+        List<Edge> edges = edgeAPI.update(req);
+        Assert.assertEquals(1, edges.size());
+        Map<String, Object> expectProperties = ImmutableMap.of("name", "tom",
+                                                               "price", 1,
+                                                               "list", list);
+        Assert.assertEquals(edges.get(0).properties(), expectProperties);
+    }
+
+    @Test
+    public void testEdgeBatchUpdateWithNullValues() {
+        BatchEdgeRequest req = batchEdgeRequest("price", 1, null,
+                                                UpdateStrategy.OVERRIDE);
+
+        List<Edge> edges = edgeAPI.update(req);
+        assertBatchResponse(edges, "price", 1);
+    }
+
+    @Test
+    public void testEdgeBatchUpdateWithInvalidArgs() {
+        BatchEdgeRequest req1 = batchEdgeRequest("list", "old", "old",
+                                                UpdateStrategy.ELIMINATE);
+        Assert.assertThrows(ServerException.class, () -> {
+            List<Edge> edges = Whitebox.getInternalState(req1, "edges");
+            edges.set(1, null);
+            Whitebox.setInternalState(req1, "edges", edges);
+            edgeAPI.update(req1);
+        }, e -> {
+            String expect = "The batch body can't contain null record";
+            Assert.assertContains(expect, e.getMessage());
+        });
+
+        BatchEdgeRequest req2 = batchEdgeRequest("list", "old", "old",
+                                                 UpdateStrategy.ELIMINATE);
+        Assert.assertThrows(ServerException.class, () -> {
+            Whitebox.setInternalState(req2, "edges", null);
+            edgeAPI.update(req2);
+        }, e -> {
+            String expect = "Parameter 'edges' can't be null";
+            Assert.assertContains(expect, e.getMessage());
+        });
+
+        BatchEdgeRequest req3 = batchEdgeRequest("list", "old", "old",
+                                                 UpdateStrategy.ELIMINATE);
+        Assert.assertThrows(ServerException.class, () -> {
+            Whitebox.setInternalState(req3, "edges", ImmutableList.of());
+            edgeAPI.update(req3);
+        }, e -> {
+            String expect = "The number of edges can't be 0";
+            Assert.assertContains(expect, e.getMessage());
+        });
+
+        BatchEdgeRequest req4 = batchEdgeRequest("list", "old", "old",
+                                                 UpdateStrategy.ELIMINATE);
+        Assert.assertThrows(ServerException.class, () -> {
+            Whitebox.setInternalState(req4, "createIfNotExist", false);
+            edgeAPI.update(req4);
+        }, e -> {
+            String expect = "Parameter 'create_if_not_exist' " +
+                            "dose not support false now";
+            Assert.assertContains(expect, e.getMessage());
+        });
+
+        BatchEdgeRequest req5 = batchEdgeRequest("list", "old", "old",
+                                                 UpdateStrategy.ELIMINATE);
+        Assert.assertThrows(ServerException.class, () -> {
+            Whitebox.setInternalState(req5, "updateStrategies", null);
+            edgeAPI.update(req5);
+        }, e -> {
+            String expect = "Parameter 'update_strategies' can't be empty";
+            Assert.assertContains(expect, e.getMessage());
+        });
+
+        BatchEdgeRequest req6 = batchEdgeRequest("list", "old", "old",
+                                                 UpdateStrategy.ELIMINATE);
+        Assert.assertThrows(ServerException.class, () -> {
+            Whitebox.setInternalState(req6, "updateStrategies",
+                                      ImmutableMap.of());
+            edgeAPI.update(req6);
+        }, e -> {
+            String expect = "Parameter 'update_strategies' can't be empty";
+            Assert.assertContains(expect, e.getMessage());
+        });
+
+        BatchEdgeRequest req7 = batchEdgeRequest("list", "old", "old",
+                                                 UpdateStrategy.ELIMINATE);
+        Assert.assertThrows(ServerException.class, () -> {
+            List<Edge> edges = this.createNEdgesBatch("object", "updates",
+                                                      "old", 501);
+            Whitebox.setInternalState(req7, "edges", edges);
+            edgeAPI.update(req7);
+        }, e -> {
+            String expect = "Too many edges for one time post";
+            Assert.assertContains(expect, e.getMessage());
+        });
+    }
+
+    @Test
+    public void testEdgeInvalidUpdateStrategy() {
+        BatchEdgeRequest req1 = batchEdgeRequest("name", "old", "new",
+                                                 UpdateStrategy.SUM);
+        Assert.assertThrows(ServerException.class, () -> {
+            edgeAPI.update(req1);
+        }, e -> {
+            String expect = "Property type must be Number for strategy SUM, " +
+                            "but got type String, String";
+            Assert.assertContains(expect, e.getMessage());
+        });
+
+        BatchEdgeRequest req2 = batchEdgeRequest("name", "old", "new",
+                                                 UpdateStrategy.BIGGER);
+        Assert.assertThrows(ServerException.class, () -> {
+            edgeAPI.update(req2);
+        }, e -> {
+            String expect = "Property type must be Date or Number " +
+                            "for strategy BIGGER, but got type String, String";
+            Assert.assertContains(expect, e.getMessage());
+        });
+
+        BatchEdgeRequest req3 = batchEdgeRequest("name", "old", "new",
+                                                 UpdateStrategy.SMALLER);
+        Assert.assertThrows(ServerException.class, () -> {
+            edgeAPI.update(req3);
+        }, e -> {
+            String expect = "Property type must be Date or Number " +
+                            "for strategy SMALLER, but got type String, String";
+            Assert.assertContains(expect, e.getMessage());
+        });
+
+        BatchEdgeRequest req4 = batchEdgeRequest("price", 1, -1,
+                                                 UpdateStrategy.UNION);
+        Assert.assertThrows(ServerException.class, () -> {
+            edgeAPI.update(req4);
+        }, e -> {
+            String expect = "Property type must be Set or List " +
+                            "for strategy UNION, but got type Integer, Integer";
+            Assert.assertContains(expect, e.getMessage());
+        });
+
+        BatchEdgeRequest req5 = batchEdgeRequest("date", "old", "new",
+                                                 INTERSECTION);
+        Assert.assertThrows(ServerException.class, () -> {
+            edgeAPI.update(req5);
+        }, e -> {
+            String expect = "Property type must be Set or List for " +
+                            "strategy INTERSECTION, but got type Date, Long";
+            Assert.assertContains(expect, e.getMessage());
+        });
+
+        BatchEdgeRequest req6 = batchEdgeRequest("price", 1, -1,
+                                                 UpdateStrategy.APPEND);
+        Assert.assertThrows(ServerException.class, () -> {
+            edgeAPI.update(req6);
+        }, e -> {
+            String expect = "Property type must be Set or List for " +
+                            "strategy APPEND, but got type Integer, Integer";
+            Assert.assertContains(expect, e.getMessage());
+        });
+
+        BatchEdgeRequest req7 = batchEdgeRequest("name", "old", "new",
+                                                 UpdateStrategy.ELIMINATE);
+        Assert.assertThrows(ServerException.class, () -> {
+            edgeAPI.update(req7);
+        }, e -> {
+            String expect = "Property type must be Set or List for " +
+                            "strategy ELIMINATE, but got type String, String";
+            Assert.assertContains(expect, e.getMessage());
+        });
+    }
+
+    private BatchVertexRequest batchVertexRequest(String key, Object oldData,
+                                                  Object newData,
+                                                  UpdateStrategy strategy) {
+        // Init old & new vertices
+        graph().addVertices(this.createNVertexBatch("object", oldData,
+                                                    BATCH_SIZE));
+        List<Vertex> vertices = this.createNVertexBatch("object", newData,
+                                                        BATCH_SIZE);
+
+        Map<String, UpdateStrategy> strategies = ImmutableMap.of(key, strategy);
+        BatchVertexRequest req;
+        req = new BatchVertexRequest.Builder().vertices(vertices)
+                                              .updatingStrategies(strategies)
+                                              .createIfNotExist(true)
+                                              .build();
+        return req;
+    }
+
+    private BatchEdgeRequest batchEdgeRequest(String key, Object oldData,
+                                              Object newData,
+                                              UpdateStrategy strategy) {
+        // Init old vertices & edges
+        graph().addVertices(this.createNVertexBatch("object", oldData,
+                                                    BATCH_SIZE * 2));
+        graph().addEdges(this.createNEdgesBatch("object", "updates",
+                                                oldData, BATCH_SIZE));
+        List<Edge> edges = this.createNEdgesBatch("object", "updates",
+                                                  newData, BATCH_SIZE);
+
+        Map<String, UpdateStrategy> strategies = ImmutableMap.of(key, strategy);
+        BatchEdgeRequest req;
+        req = new BatchEdgeRequest.Builder().edges(edges)
+                                            .updatingStrategies(strategies)
+                                            .checkVertex(false)
+                                            .createIfNotExist(true)
+                                            .build();
+        return req;
+    }
+
+    private List<Vertex> createNVertexBatch(String vertexLabel,
+                                            Object symbol, int num) {
+        List<Vertex> vertices = new ArrayList<>(num);
+        for (int i = 1; i <= num; i++) {
+            Vertex vertex = new Vertex(vertexLabel);
+            vertex.property("name", String.valueOf(i));
+            if (symbol instanceof Number) {
+                vertex.property("price", (int) symbol * i);
+            }
+            vertex.property("date", new Date(System.currentTimeMillis() + i));
+            vertex.property("set", ImmutableSet.of(String.valueOf(symbol) + i));
+            vertex.property("list",
+                            ImmutableList.of(String.valueOf(symbol) + i));
+            vertices.add(vertex);
+        }
+        return vertices;
+    }
+
+    private List<Edge> createNEdgesBatch(String vertexLabel, String edgeLabel,
+                                         Object symbol, int num) {
+        VertexLabel vLabel = schema().getVertexLabel(vertexLabel);
+
+        List<Edge> edges = new ArrayList<>(num);
+        for (int i = 1; i <= num; i++) {
+            Edge edge = new Edge(edgeLabel);
+            edge.sourceLabel(vertexLabel);
+            edge.targetLabel(vertexLabel);
+            edge.sourceId(vLabel.id() + ":" + i);
+            edge.targetId(vLabel.id() + ":" + i * 2);
+            edge.property("name", String.valueOf(i));
+            if (symbol instanceof Number) {
+                edge.property("price", (int) symbol * i);
+            }
+            edge.property("date", new Date(System.currentTimeMillis() + i));
+            edge.property("set", ImmutableSet.of(String.valueOf(symbol) + i));
+            edge.property("list", ImmutableList.of(String.valueOf(symbol) + i));
+            edges.add(edge);
+        }
+        return edges;
+    }
+
+    private static void assertBatchResponse(List<? extends GraphElement> list,
+                                            String property, int result) {
+        Assert.assertEquals(BATCH_SIZE, list.size());
+        list.forEach(element -> {
+            String index = String.valueOf(element.property("name"));
+            Object value = element.property(property);
+            Assert.assertTrue(value instanceof Number);
+            Assert.assertEquals(result * Integer.parseInt(index), value);
+        });
+    }
+
+    private static void assertBatchResponse(List<? extends GraphElement> list,
+                                            String property, String... data) {
+        Assert.assertEquals(BATCH_SIZE, list.size());
+        list.forEach(element -> {
+            String index = String.valueOf(element.property("name"));
+            Object value = element.property(property);
+            Assert.assertTrue(value instanceof List);
+            if (data.length == 0) {
+                Assert.assertTrue(((List<?>) value).isEmpty());
+            } else if (data.length == 1) {
+                Assert.assertEquals(ImmutableList.of(data[0] + index), value);
+            } else {
+                Assert.assertEquals(ImmutableList.of(data[0] + index,
+                                                     data[1] + index), value);
+            }
+        });
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/api/EdgeApiTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/EdgeApiTest.java
new file mode 100644
index 0000000..9ab8dbb
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/EdgeApiTest.java
@@ -0,0 +1,761 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.After;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.baidu.hugegraph.driver.SchemaManager;
+import com.baidu.hugegraph.exception.ServerException;
+import com.baidu.hugegraph.structure.constant.T;
+import com.baidu.hugegraph.structure.graph.Edge;
+import com.baidu.hugegraph.structure.graph.Vertex;
+import com.baidu.hugegraph.structure.schema.VertexLabel;
+import com.baidu.hugegraph.testutil.Assert;
+import com.baidu.hugegraph.testutil.Utils;
+import com.baidu.hugegraph.util.DateUtil;
+import com.google.common.collect.ImmutableMap;
+
+public class EdgeApiTest extends BaseApiTest {
+
+    @BeforeClass
+    public static void prepareSchema() {
+        BaseApiTest.initPropertyKey();
+        BaseApiTest.initVertexLabel();
+        BaseApiTest.initEdgeLabel();
+        BaseApiTest.initVertex();
+    }
+
+    @Override
+    @After
+    public void teardown() {
+        edgeAPI.list(-1).results().forEach(e -> edgeAPI.delete(e.id()));
+    }
+
+    @Test
+    public void testCreate() {
+        Object outVId = getVertexId("person", "name", "peter");
+        Object inVId = getVertexId("software", "name", "lop");
+
+        Edge edge = new Edge("created");
+        edge.sourceLabel("person");
+        edge.targetLabel("software");
+        edge.sourceId(outVId);
+        edge.targetId(inVId);
+        edge.property("date", "2017-03-24");
+        edge.property("city", "Hongkong");
+
+        edge = edgeAPI.create(edge);
+
+        Assert.assertEquals("created", edge.label());
+        Assert.assertEquals("person", edge.sourceLabel());
+        Assert.assertEquals("software", edge.targetLabel());
+        Assert.assertEquals(outVId, edge.sourceId());
+        Assert.assertEquals(inVId, edge.targetId());
+        String date = Utils.formatDate("2017-03-24");
+        Map<String, Object> props = ImmutableMap.of("date", date,
+                                                    "city", "Hongkong");
+        Assert.assertEquals(props, edge.properties());
+    }
+
+    @Test
+    public void testCreateWithUndefinedLabel() {
+        Object outVId = getVertexId("person", "name", "peter");
+        Object inVId = getVertexId("software", "name", "lop");
+
+        Edge edge = new Edge("undefined");
+        edge.sourceLabel("person");
+        edge.targetLabel("software");
+        edge.sourceId(outVId);
+        edge.targetId(inVId);
+        edge.property("date", "2017-03-24");
+        edge.property("city", "Hongkong");
+
+        Utils.assertResponseError(400, () -> {
+            edgeAPI.create(edge);
+        });
+    }
+
+    @Test
+    public void testCreateWithUndefinedPropertyKey() {
+        Object outVId = getVertexId("person", "name", "peter");
+        Object inVId = getVertexId("software", "name", "lop");
+
+        Edge edge = new Edge("created");
+        edge.sourceLabel("person");
+        edge.targetLabel("software");
+        edge.sourceId(outVId);
+        edge.targetId(inVId);
+        edge.property("not-exist-key", "not-exist-value");
+        edge.property("city", "Hongkong");
+
+        Utils.assertResponseError(400, () -> {
+            edgeAPI.create(edge);
+        });
+    }
+
+    @Test
+    public void testCreateWithoutSourceOrTargetLabel() {
+        Object outVId = getVertexId("person", "name", "peter");
+        Object inVId = getVertexId("software", "name", "lop");
+
+        Edge edge = new Edge("created");
+        edge.sourceId(outVId);
+        edge.targetId(inVId);
+        edge.property("date", "2017-03-24");
+        edge.property("city", "Hongkong");
+
+        edge = edgeAPI.create(edge);
+
+        Assert.assertEquals("created", edge.label());
+        Assert.assertEquals("person", edge.sourceLabel());
+        Assert.assertEquals("software", edge.targetLabel());
+        Assert.assertEquals(outVId, edge.sourceId());
+        Assert.assertEquals(inVId, edge.targetId());
+        String date = Utils.formatDate("2017-03-24");
+        Map<String, Object> props = ImmutableMap.of("date", date,
+                                                    "city", "Hongkong");
+        Assert.assertEquals(props, edge.properties());
+    }
+
+    @Test
+    public void testCreateWithUndefinedSourceOrTargetLabel() {
+        Edge edge = new Edge("created");
+        edge.sourceLabel("undefined");
+        edge.targetLabel("undefined");
+        edge.sourceId("person:peter");
+        edge.targetId("software:lop");
+        edge.property("date", "2017-03-24");
+        edge.property("city", "Hongkong");
+
+        Utils.assertResponseError(400, () -> {
+            edgeAPI.create(edge);
+        });
+    }
+
+    @Test
+    public void testCreateWithoutSourceOrTargetId() {
+        Edge edge = new Edge("created");
+        edge.sourceLabel("person");
+        edge.targetLabel("software");
+        edge.property("date", "2017-03-24");
+        edge.property("city", "Hongkong");
+
+        Utils.assertResponseError(400, () -> {
+            edgeAPI.create(edge);
+        });
+    }
+
+    @Test
+    public void testCreateWithNotExistSourceOrTargetId() {
+        Edge edge = new Edge("created");
+        edge.sourceId("not-exist-source-id");
+        edge.targetId("not-exist-target-id");
+        edge.sourceLabel("person");
+        edge.targetLabel("software");
+        edge.property("date", "2017-03-24");
+        edge.property("city", "Hongkong");
+
+        Utils.assertResponseError(400, () -> {
+            edgeAPI.create(edge);
+        });
+    }
+
+    @Test
+    public void testCreateExistVertex() {
+        Object outVId = getVertexId("person", "name", "peter");
+        Object inVId = getVertexId("software", "name", "lop");
+
+        Edge edge = new Edge("created");
+        edge.sourceLabel("person");
+        edge.targetLabel("software");
+        edge.sourceId(outVId);
+        edge.targetId(inVId);
+        edge.property("date", "2017-03-24");
+        edge.property("city", "Hongkong");
+        edgeAPI.create(edge);
+
+        edge = new Edge("created");
+        edge.sourceLabel("person");
+        edge.targetLabel("software");
+        edge.sourceId(outVId);
+        edge.targetId(inVId);
+        edge.property("date", Utils.date("2017-03-24"));
+        edge.property("city", "Beijing");
+
+        Assert.assertEquals("created", edge.label());
+        Assert.assertEquals("person", edge.sourceLabel());
+        Assert.assertEquals("software", edge.targetLabel());
+        Assert.assertEquals(outVId, edge.sourceId());
+        Assert.assertEquals(inVId, edge.targetId());
+        Map<String, Object> props = ImmutableMap.of(
+                                    "date", Utils.date("2017-03-24"),
+                                    "city", "Beijing");
+        Assert.assertEquals(props, edge.properties());
+    }
+
+    @Test
+    public void testCreateWithNullableKeysAbsent() {
+        Object outVId = getVertexId("person", "name", "peter");
+        Object inVId = getVertexId("software", "name", "lop");
+
+        Edge edge = new Edge("created");
+        edge.sourceLabel("person");
+        edge.targetLabel("software");
+        edge.sourceId(outVId);
+        edge.targetId(inVId);
+        // Absent prop 'city'
+        edge.property("date", Utils.date("2017-03-24"));
+        edgeAPI.create(edge);
+
+        Assert.assertEquals("created", edge.label());
+        Assert.assertEquals("person", edge.sourceLabel());
+        Assert.assertEquals("software", edge.targetLabel());
+        Assert.assertEquals(outVId, edge.sourceId());
+        Assert.assertEquals(inVId, edge.targetId());
+        Map<String, Object> props = ImmutableMap.of("date",
+                                                    Utils.date("2017-03-24"));
+        Assert.assertEquals(props, edge.properties());
+    }
+
+    @Test
+    public void testCreateWithNonNullKeysAbsent() {
+        Edge edge = new Edge("created");
+        edge.sourceLabel("person");
+        edge.targetLabel("software");
+        edge.sourceId("person:peter");
+        edge.targetId("software:lop");
+        // Absent prop 'date'
+        edge.property("city", "Beijing");
+
+        Utils.assertResponseError(400, () -> {
+            edgeAPI.create(edge);
+        });
+    }
+
+    @Test
+    public void testAddEdgeWithTtl() {
+        SchemaManager schema = schema();
+        schema.propertyKey("place").asText().ifNotExist().create();
+        schema.edgeLabel("read").link("person", "book")
+              .properties("place", "date")
+              .ttl(3000L)
+              .enableLabelIndex(true)
+              .ifNotExist()
+              .create();
+
+        Vertex baby = graph().addVertex(T.label, "person", "name", "Baby",
+                                        "age", 3, "city", "Beijing");
+        Vertex java = graph().addVertex(T.label, "book", T.id, "java",
+                                        "name", "Java in action");
+        Edge edge = baby.addEdge("read", java, "place", "library of school",
+                                 "date", "2019-12-23 12:00:00");
+
+        Edge result = graph().getEdge(edge.id());
+        Assert.assertEquals("read", result.label());
+        Assert.assertEquals("person", edge.sourceLabel());
+        Assert.assertEquals("book", edge.targetLabel());
+        Assert.assertEquals(baby.id(), edge.sourceId());
+        Assert.assertEquals(java.id(), edge.targetId());
+        Map<String, Object> props = ImmutableMap.of("place",
+                                                    "library of school",
+                                                    "date",
+                                                    "2019-12-23 12:00:00.000");
+        Assert.assertEquals(props, result.properties());
+
+        try {
+            Thread.sleep(1100L);
+        } catch (InterruptedException e) {
+            // Ignore
+        }
+
+        result = graph().getEdge(edge.id());
+        Assert.assertEquals("read", result.label());
+        Assert.assertEquals("person", edge.sourceLabel());
+        Assert.assertEquals("book", edge.targetLabel());
+        Assert.assertEquals(baby.id(), edge.sourceId());
+        Assert.assertEquals(java.id(), edge.targetId());
+        Assert.assertEquals(props, result.properties());
+
+        try {
+            Thread.sleep(1100L);
+        } catch (InterruptedException e) {
+            // Ignore
+        }
+
+        result = graph().getEdge(edge.id());
+        Assert.assertEquals("read", result.label());
+        Assert.assertEquals("person", edge.sourceLabel());
+        Assert.assertEquals("book", edge.targetLabel());
+        Assert.assertEquals(baby.id(), edge.sourceId());
+        Assert.assertEquals(java.id(), edge.targetId());
+        Assert.assertEquals(props, result.properties());
+
+        try {
+            Thread.sleep(1100L);
+        } catch (InterruptedException e) {
+            // Ignore
+        }
+
+        Assert.assertThrows(ServerException.class, () -> {
+            graph().getEdge(edge.id());
+        }, e -> {
+            Assert.assertContains("does not exist", e.getMessage());
+        });
+    }
+
+    @Test
+    public void testAddEdgeWithTtlAndTtlStartTime() {
+        SchemaManager schema = schema();
+        schema.propertyKey("place").asText().ifNotExist().create();
+        schema.edgeLabel("borrow").link("person", "book")
+              .properties("place", "date")
+              .ttl(3000L)
+              .ttlStartTime("date")
+              .enableLabelIndex(true)
+              .ifNotExist()
+              .create();
+
+        Vertex baby = graph().addVertex(T.label, "person", "name", "Baby",
+                                        "age", 3, "city", "Beijing");
+        Vertex java = graph().addVertex(T.label, "book", T.id, "java",
+                                        "name", "Java in action");
+        long date = DateUtil.now().getTime() - 1000L;
+        String dateString = Utils.formatDate(new Date(date));
+        Edge edge = baby.addEdge("borrow", java, "place", "library of school",
+                                 "date", date);
+
+        Edge result = graph().getEdge(edge.id());
+        Assert.assertEquals("borrow", result.label());
+        Assert.assertEquals("person", edge.sourceLabel());
+        Assert.assertEquals("book", edge.targetLabel());
+        Assert.assertEquals(baby.id(), edge.sourceId());
+        Assert.assertEquals(java.id(), edge.targetId());
+        Map<String, Object> props = ImmutableMap.of("place",
+                                                    "library of school",
+                                                    "date",
+                                                    dateString);
+        Assert.assertEquals(props, result.properties());
+
+        try {
+            Thread.sleep(1100L);
+        } catch (InterruptedException e) {
+            // Ignore
+        }
+
+        result = graph().getEdge(edge.id());
+        Assert.assertEquals("borrow", result.label());
+        Assert.assertEquals("person", edge.sourceLabel());
+        Assert.assertEquals("book", edge.targetLabel());
+        Assert.assertEquals(baby.id(), edge.sourceId());
+        Assert.assertEquals(java.id(), edge.targetId());
+        Assert.assertEquals(props, result.properties());
+
+        try {
+            Thread.sleep(1100L);
+        } catch (InterruptedException e) {
+            // Ignore
+        }
+
+        Assert.assertThrows(ServerException.class, () -> {
+            graph().getEdge(edge.id());
+        }, e -> {
+            Assert.assertContains("does not exist", e.getMessage());
+        });
+    }
+
+    @Test
+    public void testBatchCreateWithValidVertexAndCheck() {
+        VertexLabel person = schema().getVertexLabel("person");
+        VertexLabel software = schema().getVertexLabel("software");
+
+        List<Vertex> persons = super.create100PersonBatch();
+        List<Vertex> softwares = super.create50SoftwareBatch();
+
+        vertexAPI.create(persons);
+        vertexAPI.create(softwares);
+
+        List<Edge> createds = super.create50CreatedBatch();
+        List<Edge> knows = super.create50KnowsBatch();
+
+        List<String> createdIds = edgeAPI.create(createds, true);
+        List<String> knowsIds = edgeAPI.create(knows, true);
+
+        Assert.assertEquals(50, createdIds.size());
+        Assert.assertEquals(50, knowsIds.size());
+
+        for (int i = 0; i < 50; i++) {
+            Edge created = edgeAPI.get(createdIds.get(i));
+            Assert.assertEquals("created", created.label());
+            Assert.assertEquals("person", created.sourceLabel());
+            Assert.assertEquals("software", created.targetLabel());
+            Assert.assertEquals(person.id() + ":Person-" + i, created.sourceId());
+            Assert.assertEquals(software.id() + ":Software-" + i,
+                                created.targetId());
+            String date = Utils.formatDate("2017-03-24");
+            Map<String, Object> props = ImmutableMap.of("date", date,
+                                                        "city", "Hongkong");
+            Assert.assertEquals(props, created.properties());
+        }
+
+        for (int i = 0; i < 50; i++) {
+            Edge know = edgeAPI.get(knowsIds.get(i));
+            Assert.assertEquals("knows", know.label());
+            Assert.assertEquals("person", know.sourceLabel());
+            Assert.assertEquals("person", know.targetLabel());
+            Assert.assertEquals(person.id() + ":Person-" + i, know.sourceId());
+            Assert.assertEquals(person.id() + ":Person-" + (i + 50),
+                                know.targetId());
+            String date = Utils.formatDate("2017-03-24");
+            Map<String, Object> props = ImmutableMap.of("date", date);
+            Assert.assertEquals(props, know.properties());
+        }
+    }
+
+    @Test
+    public void testBatchCreateContainsInvalidEdge() {
+        List<Vertex> persons = super.create100PersonBatch();
+        List<Vertex> softwares = super.create50SoftwareBatch();
+
+        vertexAPI.create(persons);
+        vertexAPI.create(softwares);
+
+        List<Edge> createds = super.create50CreatedBatch();
+        List<Edge> knows = super.create50KnowsBatch();
+
+        createds.get(0).sourceId("not-exist-sourceId-id");
+        knows.get(10).property("undefined-key", "undefined-value");
+
+        Utils.assertResponseError(400, () -> {
+            edgeAPI.create(createds, true);
+        });
+        Utils.assertResponseError(400, () -> {
+            edgeAPI.create(knows, true);
+        });
+    }
+
+    @Test
+    public void testBatchCreateWithMoreThanBatchSize() {
+        List<Vertex> persons = super.create100PersonBatch();
+        List<Vertex> softwares = super.create50SoftwareBatch();
+
+        vertexAPI.create(persons);
+        vertexAPI.create(softwares);
+
+        List<Edge> edges = new ArrayList<>(1000);
+        for (int i = 0; i < 1000; i++) {
+            Edge edge = new Edge("created");
+            edge.sourceLabel("person");
+            edge.targetLabel("software");
+            edge.sourceId("person:Person-" + i);
+            edge.targetId("software:Software-" + i);
+            edge.property("date", "2017-08-24");
+            edge.property("city", "Hongkong");
+            edges.add(edge);
+        }
+        Utils.assertResponseError(400, () -> {
+            edgeAPI.create(edges, true);
+        });
+    }
+
+    @Test
+    public void testBatchCreateWithValidVertexAndNotCheck() {
+        VertexLabel person = schema().getVertexLabel("person");
+        VertexLabel software = schema().getVertexLabel("software");
+
+        List<Vertex> persons = super.create100PersonBatch();
+        List<Vertex> softwares = super.create50SoftwareBatch();
+
+        vertexAPI.create(persons);
+        vertexAPI.create(softwares);
+
+        List<Edge> createds = super.create50CreatedBatch();
+        List<Edge> knows = super.create50KnowsBatch();
+
+        List<String> createdIds = edgeAPI.create(createds, false);
+        List<String> knowsIds = edgeAPI.create(knows, false);
+
+        Assert.assertEquals(50, createdIds.size());
+        Assert.assertEquals(50, knowsIds.size());
+
+        for (int i = 0; i < 50; i++) {
+            Edge created = edgeAPI.get(createdIds.get(i));
+            Assert.assertEquals("created", created.label());
+            Assert.assertEquals("person", created.sourceLabel());
+            Assert.assertEquals("software", created.targetLabel());
+            Assert.assertEquals(person.id() + ":Person-" + i, created.sourceId());
+            Assert.assertEquals(software.id() + ":Software-" + i,
+                                created.targetId());
+            String date = Utils.formatDate("2017-03-24");
+            Map<String, Object> props = ImmutableMap.of("date", date,
+                                                        "city", "Hongkong");
+            Assert.assertEquals(props, created.properties());
+        }
+
+        for (int i = 0; i < 50; i++) {
+            Edge know = edgeAPI.get(knowsIds.get(i));
+            Assert.assertEquals("knows", know.label());
+            Assert.assertEquals("person", know.sourceLabel());
+            Assert.assertEquals("person", know.targetLabel());
+            Assert.assertEquals(person.id() + ":Person-" + i, know.sourceId());
+            Assert.assertEquals(person.id() + ":Person-" + (i + 50),
+                                know.targetId());
+            String date = Utils.formatDate("2017-03-24");
+            Map<String, Object> props = ImmutableMap.of("date", date);
+            Assert.assertEquals(props, know.properties());
+        }
+    }
+
+    @Test
+    public void testBatchCreateWithInvalidVertexIdAndCheck() {
+        List<Edge> edges = new ArrayList<>(2);
+
+        Edge edge1 = new Edge("created");
+        edge1.sourceLabel("person");
+        edge1.targetLabel("software");
+        edge1.sourceId("person:invalid");
+        edge1.targetId("software:lop");
+        edge1.property("date", "2017-03-24");
+        edge1.property("city", "Hongkong");
+        edges.add(edge1);
+
+        Edge edge2 = new Edge("knows");
+        edge2.sourceLabel("person");
+        edge2.targetLabel("person");
+        edge2.sourceId("person:peter");
+        edge2.targetId("person:invalid");
+        edge2.property("date", "2017-03-24");
+        edges.add(edge2);
+
+        Utils.assertResponseError(400, () -> {
+            edgeAPI.create(edges, true);
+        });
+    }
+
+    @Test
+    public void testBatchCreateWithInvalidVertexLabelAndCheck() {
+        List<Edge> edges = new ArrayList<>(2);
+
+        Edge edge1 = new Edge("created");
+        edge1.sourceId("person:peter");
+        edge1.targetId("software:lop");
+        edge1.property("date", "2017-03-24");
+        edge1.property("city", "Hongkong");
+        edges.add(edge1);
+
+        Edge edge2 = new Edge("knows");
+        edge2.sourceLabel("undefined");
+        edge2.targetLabel("undefined");
+        edge2.sourceId("person:peter");
+        edge2.targetId("person:marko");
+        edge2.property("date", "2017-03-24");
+        edges.add(edge2);
+
+        Utils.assertResponseError(400, () -> {
+            edgeAPI.create(edges, true);
+        });
+    }
+
+    /**
+     * Note: When the vertex of an edge is dirty (id), g.E() will
+     * construct the vertex, and then throw a illegal exception.
+     * That will lead clearData error.
+     * (Icafe: HugeGraph-768)
+     */
+//    @Test
+//    public void testBatchCreateWithInvalidVertexIdButNotCheck() {
+//        List<Edge> edges = new ArrayList<>(2);
+//
+//        Edge edge1 = new Edge("created");
+//        edge1.sourceLabel("person");
+//        edge1.targetLabel("software");
+//        edge1.sourceId("person:invalid");
+//        edge1.targetId("software:lop");
+//        edge1.property("date", "2017-03-24");
+//        edge1.property("city", "Hongkong");
+//        edges.add(edge1);
+//
+//        Edge edge2 = new Edge("knows");
+//        edge2.sourceLabel("person");
+//        edge2.targetLabel("person");
+//        edge2.sourceId("person:peter");
+//        edge2.targetId("person:invalid");
+//        edge2.property("date", "2017-03-24");
+//        edges.add(edge2);
+//
+//        List<String> ids = edgeAPI.create(edges, false);
+//        Assert.assertEquals(2, ids.size());
+//
+//        Utils.assertErrorResponse(404, () -> {
+//            edgeAPI.get(ids.get(0));
+//        });
+//        Utils.assertErrorResponse(404, () -> {
+//            edgeAPI.get(ids.get(1));
+//        });
+//    }
+
+    @Test
+    public void testBatchCreateWithInvalidVertexLabelButNotCheck() {
+        List<Edge> edges = new ArrayList<>(2);
+
+        Edge edge1 = new Edge("created");
+        edge1.sourceId("person:peter");
+        edge1.targetId("software:lop");
+        edge1.property("date", "2017-03-24");
+        edge1.property("city", "Hongkong");
+        edges.add(edge1);
+
+        Edge edge2 = new Edge("knows");
+        edge2.sourceLabel("undefined");
+        edge2.targetLabel("undefined");
+        edge2.sourceId("person:peter");
+        edge2.targetId("person:marko");
+        edge2.property("date", "2017-03-24");
+        edges.add(edge2);
+
+        Utils.assertResponseError(400, () -> {
+            edgeAPI.create(edges, false);
+        });
+    }
+
+    @Test
+    public void testGet() {
+        Object outVId = getVertexId("person", "name", "peter");
+        Object inVId = getVertexId("software", "name", "lop");
+
+        Edge edge1 = new Edge("created");
+        edge1.sourceLabel("person");
+        edge1.targetLabel("software");
+        edge1.sourceId(outVId);
+        edge1.targetId(inVId);
+        edge1.property("date", "2017-03-24");
+        edge1.property("city", "Hongkong");
+
+        edge1 = edgeAPI.create(edge1);
+
+        Edge edge2 = edgeAPI.get(edge1.id());
+
+        Assert.assertEquals(edge1.label(), edge2.label());
+        Assert.assertEquals(edge1.sourceLabel(), edge2.sourceLabel());
+        Assert.assertEquals(edge1.targetLabel(), edge2.targetLabel());
+        Assert.assertEquals(edge1.sourceId(), edge2.sourceId());
+        Assert.assertEquals(edge1.targetId(), edge2.targetId());
+        Assert.assertEquals(edge1.properties(), edge2.properties());
+    }
+
+    @Test
+    public void testGetNotExist() {
+        String edgeId = "not-exist-edge-id";
+        Assert.assertThrows(ServerException.class, () -> {
+            // TODO: id to be modified
+            edgeAPI.get(edgeId);
+        }, e -> {
+            Assert.assertContains("Edge id must be formatted as 4~5 parts, " +
+                                  "but got 1 parts: 'not-exist-edge-id'",
+                                  e.getMessage());
+            Assert.assertInstanceOf(ServerException.class, e);
+            Assert.assertContains("NotFoundException",
+                                  ((ServerException) e).exception());
+        });
+    }
+
+    @Test
+    public void testList() {
+        List<Vertex> persons = super.create100PersonBatch();
+        List<Vertex> softwares = super.create50SoftwareBatch();
+
+        vertexAPI.create(persons);
+        vertexAPI.create(softwares);
+
+        List<Edge> createds = super.create50CreatedBatch();
+        List<Edge> knows = super.create50KnowsBatch();
+
+        edgeAPI.create(createds, true);
+        edgeAPI.create(knows, true);
+
+        List<Edge> edges = edgeAPI.list(-1).results();
+        Assert.assertEquals(100, edges.size());
+    }
+
+    @Test
+    public void testListWithLimit() {
+        List<Vertex> persons = super.create100PersonBatch();
+        List<Vertex> softwares = super.create50SoftwareBatch();
+
+        vertexAPI.create(persons);
+        vertexAPI.create(softwares);
+
+        List<Edge> createds = super.create50CreatedBatch();
+        List<Edge> knows = super.create50KnowsBatch();
+
+        edgeAPI.create(createds, true);
+        edgeAPI.create(knows, true);
+
+        List<Edge> edges = edgeAPI.list(10).results();
+        Assert.assertEquals(10, edges.size());
+    }
+
+    @Test
+    public void testDelete() {
+        Object outVId = getVertexId("person", "name", "peter");
+        Object inVId = getVertexId("software", "name", "lop");
+
+        Edge edge = new Edge("created");
+        edge.sourceLabel("person");
+        edge.targetLabel("software");
+        edge.sourceId(outVId);
+        edge.targetId(inVId);
+        edge.property("date", "2017-03-24");
+        edge.property("city", "Hongkong");
+
+        edge = edgeAPI.create(edge);
+
+        final String id = edge.id();
+        edgeAPI.delete(id);
+
+        Assert.assertThrows(ServerException.class, () -> {
+            edgeAPI.get(id);
+        }, e -> {
+            String expect = String.format("Edge '%s' does not exist", id);
+            Assert.assertContains(expect, e.getMessage());
+        });
+    }
+
+    @Test
+    public void testDeleteNotExist() {
+        Utils.assertResponseError(400, () -> {
+            edgeAPI.delete("S364:peter>213>>S365:not-found");
+        });
+        Utils.assertResponseError(400, () -> {
+            edgeAPI.delete("not-exist-e");
+        });
+    }
+
+    @SuppressWarnings("unused")
+    private static void assertContains(List<Edge> edges, Edge edge) {
+        Assert.assertTrue(Utils.contains(edges, edge));
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/api/EdgeLabelApiTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/EdgeLabelApiTest.java
new file mode 100644
index 0000000..8e4b999
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/EdgeLabelApiTest.java
@@ -0,0 +1,550 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Function;
+
+import org.junit.After;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.baidu.hugegraph.structure.constant.Frequency;
+import com.baidu.hugegraph.structure.schema.EdgeLabel;
+import com.baidu.hugegraph.testutil.Assert;
+import com.baidu.hugegraph.testutil.Utils;
+import com.baidu.hugegraph.util.DateUtil;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+
+public class EdgeLabelApiTest extends BaseApiTest {
+
+    private static Function<String, EdgeLabel> fillEdgeLabel =
+            (name) -> schema().edgeLabel(name)
+                              .sourceLabel("person")
+                              .targetLabel("software")
+                              .singleTime()
+                              .properties("date", "city")
+                              .build();
+
+    @BeforeClass
+    public static void prepareSchema() {
+        BaseApiTest.initPropertyKey();
+        BaseApiTest.initVertexLabel();
+    }
+
+    @After
+    public void teardown() throws Exception {
+        List<Long> taskIds = new ArrayList<>();
+        edgeLabelAPI.list().forEach(el -> {
+            taskIds.add(edgeLabelAPI.delete(el.name()));
+        });
+        taskIds.forEach(taskId -> waitUntilTaskCompleted(taskId));
+    }
+
+    @Test
+    public void testCreate() {
+        EdgeLabel edgeLabel = fillEdgeLabel.apply("created");
+        edgeLabel = edgeLabelAPI.create(edgeLabel);
+
+        Assert.assertEquals("created", edgeLabel.name());
+        Assert.assertEquals("person", edgeLabel.sourceLabel());
+        Assert.assertEquals("software", edgeLabel.targetLabel());
+        Assert.assertEquals(Frequency.SINGLE, edgeLabel.frequency());
+        Assert.assertEquals(true, edgeLabel.enableLabelIndex());
+        Set<String> props = ImmutableSet.of("date", "city");
+        Assert.assertTrue(props.size() == edgeLabel.properties().size());
+        Assert.assertTrue(props.containsAll(edgeLabel.properties()));
+    }
+
+    @Test
+    public void testCreateWithFrequency() {
+        EdgeLabel edgeLabel = schema().edgeLabel("created")
+                                      .sourceLabel("person")
+                                      .targetLabel("software")
+                                      .frequency(Frequency.SINGLE)
+                                      .properties("date", "city")
+                                      .enableLabelIndex(false)
+                                      .create();
+
+        Assert.assertEquals("created", edgeLabel.name());
+        Assert.assertEquals("person", edgeLabel.sourceLabel());
+        Assert.assertEquals("software", edgeLabel.targetLabel());
+        Assert.assertEquals(Frequency.SINGLE, edgeLabel.frequency());
+        Assert.assertEquals(false, edgeLabel.enableLabelIndex());
+        Set<String> props = ImmutableSet.of("date", "city");
+        Assert.assertTrue(props.size() == edgeLabel.properties().size());
+        Assert.assertTrue(props.containsAll(edgeLabel.properties()));
+    }
+
+    @Test
+    public void testCreateWithEnableLabelIndexFalse() {
+        EdgeLabel edgeLabel = schema().edgeLabel("created")
+                                      .sourceLabel("person")
+                                      .targetLabel("software")
+                                      .singleTime()
+                                      .properties("date", "city")
+                                      .enableLabelIndex(false)
+                                      .create();
+
+        Assert.assertEquals("created", edgeLabel.name());
+        Assert.assertEquals("person", edgeLabel.sourceLabel());
+        Assert.assertEquals("software", edgeLabel.targetLabel());
+        Assert.assertEquals(Frequency.SINGLE, edgeLabel.frequency());
+        Assert.assertEquals(false, edgeLabel.enableLabelIndex());
+        Set<String> props = ImmutableSet.of("date", "city");
+        Assert.assertTrue(props.size() == edgeLabel.properties().size());
+        Assert.assertTrue(props.containsAll(edgeLabel.properties()));
+    }
+
+    @Test
+    public void testCreateWithInvalidName() {
+        Utils.assertResponseError(400, () -> {
+            edgeLabelAPI.create(fillEdgeLabel.apply(""));
+        });
+        Utils.assertResponseError(400, () -> {
+            edgeLabelAPI.create(fillEdgeLabel.apply(" "));
+        });
+        Utils.assertResponseError(400, () -> {
+            edgeLabelAPI.create(fillEdgeLabel.apply("    "));
+        });
+    }
+
+    @Test
+    public void testCreateExistedEdgeLabel() {
+        edgeLabelAPI.create(fillEdgeLabel.apply("created"));
+
+        Utils.assertResponseError(400, () -> {
+            edgeLabelAPI.create(fillEdgeLabel.apply("created"));
+        });
+    }
+
+    @Test
+    public void testCreateWithUndefinedPropertyKey() {
+        EdgeLabel edgeLabel = schema().edgeLabel("created")
+                                      .sourceLabel("person")
+                                      .targetLabel("software")
+                                      .singleTime()
+                                      .properties("undefined", "city")
+                                      .build();
+
+        Utils.assertResponseError(400, () -> {
+            edgeLabelAPI.create(edgeLabel);
+        });
+    }
+
+    @Test
+    public void testCreateWithUndefinedSortKey() {
+        EdgeLabel edgeLabel = schema().edgeLabel("created")
+                                      .sourceLabel("person")
+                                      .targetLabel("software")
+                                      .multiTimes()
+                                      .properties("date", "city")
+                                      .sortKeys("undefined")
+                                      .build();
+
+        Utils.assertResponseError(400, () -> {
+            edgeLabelAPI.create(edgeLabel);
+        });
+    }
+
+    @Test
+    public void testCreateWithUndefinedNullableKeys() {
+        EdgeLabel edgeLabel = schema().edgeLabel("created")
+                                      .sourceLabel("person")
+                                      .targetLabel("software")
+                                      .singleTime()
+                                      .properties("date", "city")
+                                      .nullableKeys("undefined")
+                                      .build();
+
+        Utils.assertResponseError(400, () -> {
+            edgeLabelAPI.create(edgeLabel);
+        });
+    }
+
+    @Test
+    public void testCreateWithNonNullKeysIntersectSortKeys() {
+        EdgeLabel edgeLabel = schema().edgeLabel("created")
+                                      .sourceLabel("person")
+                                      .targetLabel("software")
+                                      .multiTimes()
+                                      .properties("date", "city")
+                                      .sortKeys("date")
+                                      .nullableKeys("date")
+                                      .build();
+
+        Utils.assertResponseError(400, () -> {
+            edgeLabelAPI.create(edgeLabel);
+        });
+
+        Utils.assertResponseError(400, () -> {
+            edgeLabelAPI.create(edgeLabel);
+        });
+    }
+
+    @Test
+    public void testCreateWithUndefinedVertexLabel() {
+        EdgeLabel edgeLabel = schema().edgeLabel("created")
+                                      .sourceLabel("programmer")
+                                      .targetLabel("software")
+                                      .singleTime()
+                                      .properties("date", "city")
+                                      .build();
+
+        Utils.assertResponseError(400, () -> {
+            edgeLabelAPI.create(edgeLabel);
+        });
+    }
+
+    @Test
+    public void testCreateWithTtl() {
+        EdgeLabel edgeLabel = schema().edgeLabel("created1")
+                                      .sourceLabel("person")
+                                      .targetLabel("software")
+                                      .frequency(Frequency.SINGLE)
+                                      .properties("date", "city")
+                                      .build();
+        edgeLabel = edgeLabelAPI.create(edgeLabel);
+
+        Assert.assertEquals("created1", edgeLabel.name());
+        Assert.assertEquals("person", edgeLabel.sourceLabel());
+        Assert.assertEquals("software", edgeLabel.targetLabel());
+        Assert.assertEquals(Frequency.SINGLE, edgeLabel.frequency());
+        Assert.assertEquals(true, edgeLabel.enableLabelIndex());
+        Set<String> props = ImmutableSet.of("date", "city");
+        Assert.assertTrue(props.size() == edgeLabel.properties().size());
+        Assert.assertTrue(props.containsAll(edgeLabel.properties()));
+        Assert.assertEquals(0L, edgeLabel.ttl());
+        Assert.assertNull(edgeLabel.ttlStartTime());
+
+        edgeLabel = schema().edgeLabel("created2")
+                            .sourceLabel("person")
+                            .targetLabel("software")
+                            .frequency(Frequency.SINGLE)
+                            .properties("date", "city")
+                            .ttl(3000L)
+                            .build();
+        edgeLabel = edgeLabelAPI.create(edgeLabel);
+
+        Assert.assertEquals("created2", edgeLabel.name());
+        Assert.assertEquals("person", edgeLabel.sourceLabel());
+        Assert.assertEquals("software", edgeLabel.targetLabel());
+        Assert.assertEquals(Frequency.SINGLE, edgeLabel.frequency());
+        Assert.assertEquals(true, edgeLabel.enableLabelIndex());
+        Assert.assertTrue(props.size() == edgeLabel.properties().size());
+        Assert.assertTrue(props.containsAll(edgeLabel.properties()));
+        Assert.assertEquals(3000L, edgeLabel.ttl());
+        Assert.assertNull(edgeLabel.ttlStartTime());
+
+        edgeLabel = schema().edgeLabel("created3")
+                            .sourceLabel("person")
+                            .targetLabel("software")
+                            .frequency(Frequency.SINGLE)
+                            .properties("date", "city")
+                            .ttl(3000L)
+                            .ttlStartTime("date")
+                            .build();
+        edgeLabel = edgeLabelAPI.create(edgeLabel);
+
+        Assert.assertEquals("created3", edgeLabel.name());
+        Assert.assertEquals("person", edgeLabel.sourceLabel());
+        Assert.assertEquals("software", edgeLabel.targetLabel());
+        Assert.assertEquals(Frequency.SINGLE, edgeLabel.frequency());
+        Assert.assertEquals(true, edgeLabel.enableLabelIndex());
+        Assert.assertTrue(props.size() == edgeLabel.properties().size());
+        Assert.assertTrue(props.containsAll(edgeLabel.properties()));
+        Assert.assertEquals(3000L, edgeLabel.ttl());
+        Assert.assertEquals("date", edgeLabel.ttlStartTime());
+    }
+
+    @Test
+    public void testAppend() {
+        EdgeLabel edgeLabel1 = schema().edgeLabel("created")
+                                       .sourceLabel("person")
+                                       .targetLabel("software")
+                                       .singleTime()
+                                       .properties("date")
+                                       .build();
+
+        edgeLabel1 = edgeLabelAPI.create(edgeLabel1);
+
+        Assert.assertEquals("created", edgeLabel1.name());
+        Assert.assertEquals("person", edgeLabel1.sourceLabel());
+        Assert.assertEquals("software", edgeLabel1.targetLabel());
+        Assert.assertEquals(Frequency.SINGLE, edgeLabel1.frequency());
+        Set<String> props = ImmutableSet.of("date");
+        Assert.assertEquals(props, edgeLabel1.properties());
+
+        EdgeLabel edgeLabel2 = schema().edgeLabel("created")
+                                       .properties("city")
+                                       .nullableKeys("city")
+                                       .build();
+        edgeLabel2 = edgeLabelAPI.append(edgeLabel2);
+
+        Assert.assertEquals("created", edgeLabel2.name());
+        Assert.assertEquals("person", edgeLabel2.sourceLabel());
+        Assert.assertEquals("software", edgeLabel2.targetLabel());
+        Assert.assertEquals(Frequency.SINGLE, edgeLabel2.frequency());
+        props = ImmutableSet.of("date", "city");
+        Set<String> nullableKeys = ImmutableSet.of("city");
+        Assert.assertEquals(props, edgeLabel2.properties());
+        Assert.assertEquals(nullableKeys, edgeLabel2.nullableKeys());
+    }
+
+    @Test
+    public void testAppendWithUndefinedPropertyKey() {
+        EdgeLabel edgeLabel1 = schema().edgeLabel("created")
+                                       .sourceLabel("person")
+                                       .targetLabel("software")
+                                       .singleTime()
+                                       .properties("date")
+                                       .build();
+        edgeLabel1 = edgeLabelAPI.create(edgeLabel1);
+
+        Assert.assertEquals("created", edgeLabel1.name());
+        Assert.assertEquals("person", edgeLabel1.sourceLabel());
+        Assert.assertEquals("software", edgeLabel1.targetLabel());
+        Assert.assertEquals(Frequency.SINGLE, edgeLabel1.frequency());
+        Set<String> props = ImmutableSet.of("date");
+        Assert.assertEquals(props, edgeLabel1.properties());
+
+        EdgeLabel edgeLabel2 = schema().edgeLabel("created")
+                                       .properties("undefined")
+                                       .build();
+        Utils.assertResponseError(400, () -> {
+            edgeLabelAPI.append(edgeLabel2);
+        });
+    }
+
+    @Test
+    public void testAppendWithUndefinedNullableKeys() {
+        EdgeLabel edgeLabel1 = schema().edgeLabel("created")
+                                       .sourceLabel("person")
+                                       .targetLabel("software")
+                                       .singleTime()
+                                       .properties("date")
+                                       .build();
+
+        edgeLabel1 = edgeLabelAPI.create(edgeLabel1);
+
+        Assert.assertEquals("created", edgeLabel1.name());
+        Assert.assertEquals("person", edgeLabel1.sourceLabel());
+        Assert.assertEquals("software", edgeLabel1.targetLabel());
+        Assert.assertEquals(Frequency.SINGLE, edgeLabel1.frequency());
+        Set<String> props = ImmutableSet.of("date");
+        Assert.assertEquals(props, edgeLabel1.properties());
+
+        EdgeLabel edgeLabel2 = schema().edgeLabel("created")
+                                .nullableKeys("undefined").build();
+        Utils.assertResponseError(400, () -> {
+            edgeLabelAPI.append(edgeLabel2);
+        });
+    }
+
+    @Test
+    public void testAppendWithSourceOrTargetLabel() {
+        EdgeLabel edgeLabel1 = schema().edgeLabel("created")
+                                       .sourceLabel("person")
+                                       .targetLabel("software")
+                                       .singleTime()
+                                       .properties("date")
+                                       .build();
+        edgeLabel1 = edgeLabelAPI.create(edgeLabel1);
+
+        Assert.assertEquals("created", edgeLabel1.name());
+        Assert.assertEquals("person", edgeLabel1.sourceLabel());
+        Assert.assertEquals("software", edgeLabel1.targetLabel());
+        Assert.assertEquals(Frequency.SINGLE, edgeLabel1.frequency());
+        Set<String> props = ImmutableSet.of("date");
+        Assert.assertEquals(props, edgeLabel1.properties());
+
+        EdgeLabel edgeLabel2 = schema().edgeLabel("created")
+                                       .sourceLabel("person")
+                                       .targetLabel("person")
+                                       .properties("city")
+                                       .build();
+
+        Utils.assertResponseError(400, () -> {
+            edgeLabelAPI.append(edgeLabel2);
+        });
+    }
+
+    @Test
+    public void testEliminate() {
+        EdgeLabel edgeLabel1 = schema().edgeLabel("created")
+                                       .sourceLabel("person")
+                                       .targetLabel("software")
+                                       .singleTime()
+                                       .properties("date")
+                                       .build();
+        edgeLabel1 = edgeLabelAPI.create(edgeLabel1);
+        Assert.assertEquals("created", edgeLabel1.name());
+        Assert.assertEquals("person", edgeLabel1.sourceLabel());
+        Assert.assertEquals("software", edgeLabel1.targetLabel());
+        Assert.assertEquals(Frequency.SINGLE, edgeLabel1.frequency());
+        Set<String> props = ImmutableSet.of("date");
+        Assert.assertEquals(props, edgeLabel1.properties());
+
+        EdgeLabel edgeLabel2 = schema().edgeLabel("created")
+                                .properties("city").build();
+        Utils.assertResponseError(400, () -> {
+            edgeLabelAPI.eliminate(edgeLabel2);
+        });
+    }
+
+    @Test
+    public void testGet() {
+        EdgeLabel edgeLabel1 = edgeLabelAPI.create(
+                               fillEdgeLabel.apply("created"));
+
+        EdgeLabel edgeLabel2 = edgeLabelAPI.get("created");
+
+        Assert.assertEquals(edgeLabel1.name(), edgeLabel2.name());
+        Assert.assertEquals(edgeLabel1.sourceLabel(), edgeLabel2.sourceLabel());
+        Assert.assertEquals(edgeLabel1.targetLabel(), edgeLabel2.targetLabel());
+        Assert.assertEquals(edgeLabel1.frequency(), edgeLabel2.frequency());
+        Assert.assertEquals(edgeLabel1.properties(), edgeLabel2.properties());
+    }
+
+    @Test
+    public void testGetNotExist() {
+        Utils.assertResponseError(404, () -> {
+            edgeLabelAPI.get("not-exist-el");
+        });
+    }
+
+    @Test
+    public void testList() {
+        EdgeLabel edgeLabel1 = schema().edgeLabel("created")
+                                       .sourceLabel("person")
+                                       .targetLabel("software")
+                                       .singleTime()
+                                       .properties("date", "city")
+                                       .build();
+        edgeLabel1 = edgeLabelAPI.create(edgeLabel1);
+
+        EdgeLabel edgeLabel2 = schema().edgeLabel("knows")
+                                       .sourceLabel("person")
+                                       .targetLabel("person")
+                                       .singleTime()
+                                       .properties("date")
+                                       .build();
+        edgeLabel2 = edgeLabelAPI.create(edgeLabel2);
+
+        List<EdgeLabel> edgeLabels = edgeLabelAPI.list();
+        Assert.assertEquals(2, edgeLabels.size());
+        assertContains(edgeLabels, edgeLabel1);
+        assertContains(edgeLabels, edgeLabel2);
+    }
+
+    @Test
+    public void testListByNames() {
+        EdgeLabel created = schema().edgeLabel("created")
+                                    .sourceLabel("person")
+                                    .targetLabel("software")
+                                    .singleTime()
+                                    .properties("date", "city")
+                                    .build();
+        created = edgeLabelAPI.create(created);
+
+        EdgeLabel knows = schema().edgeLabel("knows")
+                                  .sourceLabel("person")
+                                  .targetLabel("person")
+                                  .singleTime()
+                                  .properties("date")
+                                  .build();
+        knows = edgeLabelAPI.create(knows);
+
+        List<EdgeLabel> edgeLabels;
+
+        edgeLabels = edgeLabelAPI.list(ImmutableList.of("created"));
+        Assert.assertEquals(1, edgeLabels.size());
+        assertContains(edgeLabels, created);
+
+        edgeLabels = edgeLabelAPI.list(ImmutableList.of("knows"));
+        Assert.assertEquals(1, edgeLabels.size());
+        assertContains(edgeLabels, knows);
+
+        edgeLabels = edgeLabelAPI.list(ImmutableList.of("created", "knows"));
+        Assert.assertEquals(2, edgeLabels.size());
+        assertContains(edgeLabels, created);
+        assertContains(edgeLabels, knows);
+    }
+
+    @Test
+    public void testDelete() {
+        EdgeLabel edgeLabel = schema().edgeLabel("created")
+                                      .sourceLabel("person")
+                                      .targetLabel("software")
+                                      .singleTime()
+                                      .properties("date", "city")
+                                      .build();
+        edgeLabelAPI.create(edgeLabel);
+
+        long taskId = edgeLabelAPI.delete("created");
+        waitUntilTaskCompleted(taskId);
+
+        Utils.assertResponseError(404, () -> {
+            edgeLabelAPI.get("created");
+        });
+    }
+
+    @Test
+    public void testDeleteNotExist() {
+        Utils.assertResponseError(404, () -> {
+            edgeLabelAPI.delete("not-exist-el");
+        });
+    }
+
+    @Test
+    public void testAddEdgeLabelWithUserData() {
+        EdgeLabel father = schema().edgeLabel("father")
+                                   .link("person", "person")
+                                   .properties("weight")
+                                   .userdata("multiplicity", "one-to-many")
+                                   .build();
+        father = edgeLabelAPI.create(father);
+        Assert.assertEquals(2, father.userdata().size());
+        Assert.assertEquals("one-to-many",
+                            father.userdata().get("multiplicity"));
+        String time = (String) father.userdata().get("~create_time");
+        Date createTime = DateUtil.parse(time);
+        Assert.assertTrue(createTime.before(DateUtil.now()));
+
+        EdgeLabel write = schema().edgeLabel("write")
+                                  .link("person", "book")
+                                  .properties("date", "weight")
+                                  .userdata("multiplicity", "one-to-many")
+                                  .userdata("multiplicity", "many-to-many")
+                                  .build();
+        write = edgeLabelAPI.create(write);
+        // The same key user data will be overwritten
+        Assert.assertEquals(2, write.userdata().size());
+        Assert.assertEquals("many-to-many",
+                            write.userdata().get("multiplicity"));
+        time = (String) write.userdata().get("~create_time");
+        createTime = DateUtil.parse(time);
+        Assert.assertTrue(createTime.before(DateUtil.now()));
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/api/GraphsApiTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/GraphsApiTest.java
new file mode 100644
index 0000000..b9c9cfb
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/GraphsApiTest.java
@@ -0,0 +1,330 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.io.FileUtils;
+import org.junit.After;
+import org.junit.Test;
+
+import com.baidu.hugegraph.driver.HugeClient;
+import com.baidu.hugegraph.driver.SchemaManager;
+import com.baidu.hugegraph.rest.ClientException;
+import com.baidu.hugegraph.structure.graph.Edge;
+import com.baidu.hugegraph.structure.graph.Vertex;
+import com.baidu.hugegraph.structure.gremlin.ResultSet;
+import com.baidu.hugegraph.testutil.Assert;
+import com.google.common.collect.ImmutableSet;
+
+public class GraphsApiTest extends BaseApiTest {
+
+    private static final String GRAPH2 = "hugegraph2";
+    private static final String CONFIG2_PATH =
+                         "src/test/resources/hugegraph-create.properties";
+
+    private static final String GRAPH3 = "hugegraph3";
+    private static final String CONFIG3_PATH =
+                         "src/test/resources/hugegraph-clone.properties";
+
+    @Override
+    @After
+    public void teardown() {
+        for (String g : ImmutableSet.of(GRAPH2, GRAPH3)) {
+            try {
+                graphsAPI.get(g);
+            } catch (Exception ognored) {
+                continue;
+            }
+            graphsAPI.drop(g, "I'm sure to drop the graph");
+        }
+    }
+
+    @Test
+    public void testCreateAndDropGraph() {
+        int initialGraphNumber = graphsAPI.list().size();
+
+        // Create new graph dynamically
+        String config;
+        try {
+            config = FileUtils.readFileToString(new File(CONFIG2_PATH),
+                                                StandardCharsets.UTF_8);
+        } catch (IOException e) {
+            throw new ClientException("Failed to read config file: %s",
+                                      CONFIG2_PATH);
+        }
+        Map<String, String> result = graphsAPI.create(GRAPH2, null, config);
+        Assert.assertEquals(2, result.size());
+        Assert.assertEquals(GRAPH2, result.get("name"));
+        Assert.assertEquals("rocksdb", result.get("backend"));
+
+        Assert.assertEquals(initialGraphNumber + 1, graphsAPI.list().size());
+
+        HugeClient client = new HugeClient(baseClient(), GRAPH2);
+        // Insert graph schema and data
+        initPropertyKey(client);
+        initVertexLabel(client);
+        initEdgeLabel(client);
+
+        List<Vertex> vertices = new ArrayList<>(100);
+        for (int i = 0; i < 100; i++) {
+            Vertex vertex = new Vertex("person").property("name", "person" + i)
+                                                .property("city", "Beijing")
+                                                .property("age", 19);
+            vertices.add(vertex);
+        }
+        vertices = client.graph().addVertices(vertices);
+
+        List<Edge> edges = new ArrayList<>(100);
+        for (int i = 0; i < 100; i++) {
+            Edge edge = new Edge("knows").source(vertices.get(i))
+                                         .target(vertices.get((i + 1) % 100))
+                                         .property("date", "2016-01-10");
+            edges.add(edge);
+        }
+        client.graph().addEdges(edges, false);
+
+        // Query vertices and edges count from new created graph
+        ResultSet resultSet = client.gremlin().gremlin("g.V().count()")
+                                    .execute();
+        Assert.assertEquals(100, resultSet.iterator().next().getInt());
+
+        resultSet = client.gremlin().gremlin("g.E().count()").execute();
+        Assert.assertEquals(100, resultSet.iterator().next().getInt());
+
+        // Clear graph schema and data from new created graph
+        graphsAPI.clear(GRAPH2, "I'm sure to delete all data");
+
+        resultSet = client.gremlin().gremlin("g.V().count()").execute();
+        Assert.assertEquals(0, resultSet.iterator().next().getInt());
+
+        resultSet = client.gremlin().gremlin("g.E().count()").execute();
+        Assert.assertEquals(0, resultSet.iterator().next().getInt());
+
+        Assert.assertTrue(client.schema().getPropertyKeys().isEmpty());
+
+        Assert.assertEquals(initialGraphNumber + 1, graphsAPI.list().size());
+
+        // Remove new created graph dynamically
+        graphsAPI.drop(GRAPH2, "I'm sure to drop the graph");
+
+        Assert.assertEquals(initialGraphNumber, graphsAPI.list().size());
+    }
+
+    @Test
+    public void testCloneAndDropGraph() {
+        int initialGraphNumber = graphsAPI.list().size();
+
+        // Clone a new graph from exist a graph dynamically
+        String config;
+        try {
+            config = FileUtils.readFileToString(new File(CONFIG3_PATH),
+                                                StandardCharsets.UTF_8);
+        } catch (IOException e) {
+            throw new ClientException("Failed to read config file: %s",
+                                      CONFIG3_PATH);
+        }
+        Map<String, String> result = graphsAPI.create(GRAPH3, "hugegraph",
+                                                      config);
+        Assert.assertEquals(2, result.size());
+        Assert.assertEquals(GRAPH3, result.get("name"));
+        Assert.assertEquals("rocksdb", result.get("backend"));
+
+        Assert.assertEquals(initialGraphNumber + 1, graphsAPI.list().size());
+
+        HugeClient client = new HugeClient(baseClient(), GRAPH3);
+        // Insert graph schema and data
+        initPropertyKey(client);
+        initVertexLabel(client);
+        initEdgeLabel(client);
+
+        List<Vertex> vertices = new ArrayList<>(100);
+        for (int i = 0; i < 100; i++) {
+            Vertex vertex = new Vertex("person").property("name", "person" + i)
+                                                .property("city", "Beijing")
+                                                .property("age", 19);
+            vertices.add(vertex);
+        }
+        vertices = client.graph().addVertices(vertices);
+
+        List<Edge> edges = new ArrayList<>(100);
+        for (int i = 0; i < 100; i++) {
+            Edge edge = new Edge("knows").source(vertices.get(i))
+                                         .target(vertices.get((i + 1) % 100))
+                                         .property("date", "2016-01-10");
+            edges.add(edge);
+        }
+        client.graph().addEdges(edges, false);
+
+        // Query vertices and edges count from new created graph
+        ResultSet resultSet = client.gremlin().gremlin("g.V().count()")
+                                    .execute();
+        Assert.assertEquals(100, resultSet.iterator().next().getInt());
+
+        resultSet = client.gremlin().gremlin("g.E().count()").execute();
+        Assert.assertEquals(100, resultSet.iterator().next().getInt());
+
+        // Clear graph schema and data from new created graph
+        graphsAPI.clear(GRAPH3, "I'm sure to delete all data");
+
+        resultSet = client.gremlin().gremlin("g.V().count()").execute();
+        Assert.assertEquals(0, resultSet.iterator().next().getInt());
+
+        resultSet = client.gremlin().gremlin("g.E().count()").execute();
+        Assert.assertEquals(0, resultSet.iterator().next().getInt());
+
+        Assert.assertTrue(client.schema().getPropertyKeys().isEmpty());
+
+        Assert.assertEquals(initialGraphNumber + 1, graphsAPI.list().size());
+
+        // Remove new created graph dynamically
+        graphsAPI.drop(GRAPH3, "I'm sure to drop the graph");
+
+        Assert.assertEquals(initialGraphNumber, graphsAPI.list().size());
+    }
+
+    @Test
+    public void testCloneAndDropGraphWithoutConfig() {
+        int initialGraphNumber = graphsAPI.list().size();
+
+        // Clone a new graph from exist a graph dynamically
+        String config = null;
+        Map<String, String> result = graphsAPI.create(GRAPH3, "hugegraph",
+                                                      config);
+        Assert.assertEquals(2, result.size());
+        Assert.assertEquals(GRAPH3, result.get("name"));
+        Assert.assertEquals("rocksdb", result.get("backend"));
+
+        Assert.assertEquals(initialGraphNumber + 1, graphsAPI.list().size());
+
+        HugeClient client = new HugeClient(baseClient(), GRAPH3);
+        // Insert graph schema and data
+        initPropertyKey(client);
+        initVertexLabel(client);
+        initEdgeLabel(client);
+
+        List<Vertex> vertices = new ArrayList<>(100);
+        for (int i = 0; i < 100; i++) {
+            Vertex vertex = new Vertex("person").property("name", "person" + i)
+                                                .property("city", "Beijing")
+                                                .property("age", 19);
+            vertices.add(vertex);
+        }
+        vertices = client.graph().addVertices(vertices);
+
+        List<Edge> edges = new ArrayList<>(100);
+        for (int i = 0; i < 100; i++) {
+            Edge edge = new Edge("knows").source(vertices.get(i))
+                                         .target(vertices.get((i + 1) % 100))
+                                         .property("date", "2016-01-10");
+            edges.add(edge);
+        }
+        client.graph().addEdges(edges, false);
+
+        // Query vertices and edges count from new created graph
+        ResultSet resultSet = client.gremlin().gremlin("g.V().count()")
+                                    .execute();
+        Assert.assertEquals(100, resultSet.iterator().next().getInt());
+
+        resultSet = client.gremlin().gremlin("g.E().count()").execute();
+        Assert.assertEquals(100, resultSet.iterator().next().getInt());
+
+        // Clear graph schema and data from new created graph
+        graphsAPI.clear(GRAPH3, "I'm sure to delete all data");
+
+        resultSet = client.gremlin().gremlin("g.V().count()").execute();
+        Assert.assertEquals(0, resultSet.iterator().next().getInt());
+
+        resultSet = client.gremlin().gremlin("g.E().count()").execute();
+        Assert.assertEquals(0, resultSet.iterator().next().getInt());
+
+        Assert.assertTrue(client.schema().getPropertyKeys().isEmpty());
+
+        Assert.assertEquals(initialGraphNumber + 1, graphsAPI.list().size());
+
+        // Remove new created graph dynamically
+        graphsAPI.drop(GRAPH3, "I'm sure to drop the graph");
+
+        Assert.assertEquals(initialGraphNumber, graphsAPI.list().size());
+    }
+
+    protected static void initPropertyKey(HugeClient client) {
+        SchemaManager schema = client.schema();
+        schema.propertyKey("name").asText().ifNotExist().create();
+        schema.propertyKey("age").asInt().ifNotExist().create();
+        schema.propertyKey("city").asText().ifNotExist().create();
+        schema.propertyKey("lang").asText().ifNotExist().create();
+        schema.propertyKey("date").asDate().ifNotExist().create();
+        schema.propertyKey("price").asInt().ifNotExist().create();
+        schema.propertyKey("weight").asDouble().ifNotExist().create();
+    }
+
+    protected static void initVertexLabel(HugeClient client) {
+        SchemaManager schema = client.schema();
+
+        schema.vertexLabel("person")
+              .properties("name", "age", "city")
+              .primaryKeys("name")
+              .nullableKeys("city")
+              .ifNotExist()
+              .create();
+
+        schema.vertexLabel("software")
+              .properties("name", "lang", "price")
+              .primaryKeys("name")
+              .nullableKeys("price")
+              .ifNotExist()
+              .create();
+
+        schema.vertexLabel("book")
+              .useCustomizeStringId()
+              .properties("name", "price")
+              .nullableKeys("price")
+              .ifNotExist()
+              .create();
+    }
+
+    protected static void initEdgeLabel(HugeClient client) {
+        SchemaManager schema = client.schema();
+
+        schema.edgeLabel("knows")
+              .sourceLabel("person")
+              .targetLabel("person")
+              .multiTimes()
+              .properties("date", "city")
+              .sortKeys("date")
+              .nullableKeys("city")
+              .ifNotExist()
+              .create();
+
+        schema.edgeLabel("created")
+              .sourceLabel("person")
+              .targetLabel("software")
+              .properties("date", "city")
+              .nullableKeys("city")
+              .ifNotExist()
+              .create();
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/api/GremlinApiTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/GremlinApiTest.java
new file mode 100644
index 0000000..1962b92
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/GremlinApiTest.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.baidu.hugegraph.api.gremlin.GremlinRequest;
+import com.baidu.hugegraph.exception.ServerException;
+import com.baidu.hugegraph.structure.constant.GraphAttachable;
+import com.baidu.hugegraph.structure.graph.Edge;
+import com.baidu.hugegraph.structure.graph.Path;
+import com.baidu.hugegraph.structure.graph.Vertex;
+import com.baidu.hugegraph.structure.gremlin.Result;
+import com.baidu.hugegraph.structure.gremlin.ResultSet;
+import com.baidu.hugegraph.testutil.Assert;
+import com.baidu.hugegraph.testutil.Whitebox;
+
+public class GremlinApiTest extends BaseApiTest {
+
+    @BeforeClass
+    public static void prepareSchema() {
+        BaseApiTest.initPropertyKey();
+        BaseApiTest.initVertexLabel();
+        BaseApiTest.initEdgeLabel();
+    }
+
+    @Before
+    public void prepareData() {
+        BaseApiTest.initVertex();
+        BaseApiTest.initEdge();
+    }
+
+    @Test
+    public void testQueryAllVertices() {
+        GremlinRequest request = new GremlinRequest("g.V()");
+        ResultSet resultSet = gremlin().execute(request);
+
+        Assert.assertEquals(6, resultSet.size());
+
+        request = new GremlinRequest("g.V().drop()");
+        gremlin().execute(request);
+
+        request = new GremlinRequest("g.V()");
+        resultSet = gremlin().execute(request);
+
+        Assert.assertEquals(0, resultSet.size());
+    }
+
+    @Test
+    public void testQueryAllEdges() {
+        GremlinRequest request = new GremlinRequest("g.E()");
+        ResultSet resultSet = gremlin().execute(request);
+
+        Assert.assertEquals(6, resultSet.size());
+
+        request = new GremlinRequest("g.E().drop()");
+        gremlin().execute(request);
+
+        request = new GremlinRequest("g.E()");
+        resultSet = gremlin().execute(request);
+
+        Assert.assertEquals(0, resultSet.size());
+    }
+
+    @Test
+    public void testAsyncRemoveAllVertices() {
+        GremlinRequest request = new GremlinRequest("g.V()");
+        ResultSet resultSet = gremlin().execute(request);
+        Assert.assertEquals(6, resultSet.size());
+
+        String gremlin = "hugegraph.traversal().V().drop()";
+        request = new GremlinRequest(gremlin);
+        long id = gremlin().executeAsTask(request);
+        waitUntilTaskCompleted(id);
+
+        request = new GremlinRequest("g.V()");
+        resultSet = gremlin().execute(request);
+        Assert.assertEquals(0, resultSet.size());
+    }
+
+    @Test
+    public void testAsyncRemoveAllEdges() {
+        GremlinRequest request = new GremlinRequest("g.E()");
+        ResultSet resultSet = gremlin().execute(request);
+        Assert.assertEquals(6, resultSet.size());
+
+        String gremlin = "g.E().drop()";
+        request = new GremlinRequest(gremlin);
+        long id = gremlin().executeAsTask(request);
+        waitUntilTaskCompleted(id);
+
+        request = new GremlinRequest("g.E()");
+        resultSet = gremlin().execute(request);
+        Assert.assertEquals(0, resultSet.size());
+    }
+
+    @Test
+    public void testPrimitiveObject() {
+        GremlinRequest request = new GremlinRequest("1 + 2");
+        ResultSet resultSet = gremlin().execute(request);
+        Assert.assertEquals(1, resultSet.size());
+
+        Iterator<Result> results = resultSet.iterator();
+        while (results.hasNext()) {
+            Result result = results.next();
+            Object object = result.getObject();
+            Assert.assertEquals(Integer.class, object.getClass());
+            Assert.assertEquals(3, object);
+        }
+    }
+
+    @Test
+    public void testIterateEmptyResultSet() {
+        GremlinRequest request = new GremlinRequest("g.V().limit(0)");
+        ResultSet resultSet = gremlin().execute(request);
+        Assert.assertEquals(0, resultSet.size());
+        Assert.assertThrows(NoSuchElementException.class, () -> {
+            resultSet.iterator().next();
+        });
+    }
+
+    @Test
+    public void testAttachedManager() {
+        GremlinRequest request = new GremlinRequest("g.V()");
+        ResultSet resultSet = gremlin().execute(request);
+        Assert.assertEquals(6, resultSet.size());
+
+        Iterator<Result> results = resultSet.iterator();
+        while (results.hasNext()) {
+            Result result = results.next();
+            Object object = result.getObject();
+            Assert.assertEquals(Vertex.class, object.getClass());
+            Vertex vertex = (Vertex) object;
+            Assert.assertNotNull(Whitebox.getInternalState(vertex, "manager"));
+        }
+
+        request = new GremlinRequest("g.E()");
+        resultSet = gremlin().execute(request);
+        Assert.assertEquals(6, resultSet.size());
+
+        results = resultSet.iterator();
+        while (results.hasNext()) {
+            Result result = results.next();
+            Object object = result.getObject();
+            Assert.assertEquals(Edge.class, object.getClass());
+            Edge edge = (Edge) object;
+            Assert.assertNotNull(Whitebox.getInternalState(edge, "manager"));
+        }
+
+        request = new GremlinRequest("g.V().outE().path()");
+        resultSet = gremlin().execute(request);
+        Assert.assertEquals(6, resultSet.size());
+
+        results = resultSet.iterator();
+        while (results.hasNext()) {
+            Result result = results.next();
+            Object object = result.getObject();
+            Assert.assertEquals(Path.class, object.getClass());
+            Path path = (Path) object;
+            Assert.assertNotNull(path.objects());
+            for (Object pathObject : path.objects()) {
+                Assert.assertTrue(pathObject instanceof GraphAttachable);
+                Assert.assertNotNull(Whitebox.getInternalState(pathObject,
+                                                               "manager"));
+            }
+            Assert.assertNull(path.crosspoint());
+        }
+    }
+
+    @Test
+    public void testInvalidGremlin() {
+        Assert.assertThrows(ServerException.class, () -> {
+            client().post("gremlin", "{");
+        }, e -> {
+            Assert.assertContains("body could not be parsed", e.getMessage());
+        });
+
+        GremlinRequest request = new GremlinRequest("g.V2()");
+        Assert.assertThrows(ServerException.class, () -> {
+            gremlin().execute(request);
+        }, e -> {
+            Assert.assertContains("No signature of method: ", e.getMessage());
+            Assert.assertContains(".V2() is applicable for argument types: ()",
+                                  e.getMessage());
+        });
+    }
+
+    @Test
+    public void testSecurityOperation() {
+        GremlinRequest request = new GremlinRequest("System.exit(-1)");
+        Assert.assertThrows(ServerException.class, () -> {
+            gremlin().execute(request);
+        }, e -> {
+            String msg = "Not allowed to call System.exit() via Gremlin";
+            Assert.assertContains(msg, e.getMessage());
+        });
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/api/IndexLabelApiTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/IndexLabelApiTest.java
new file mode 100644
index 0000000..f4bc7bf
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/IndexLabelApiTest.java
@@ -0,0 +1,490 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+
+import org.junit.After;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.baidu.hugegraph.exception.NotSupportException;
+import com.baidu.hugegraph.exception.ServerException;
+import com.baidu.hugegraph.structure.Task;
+import com.baidu.hugegraph.structure.constant.HugeType;
+import com.baidu.hugegraph.structure.constant.IndexType;
+import com.baidu.hugegraph.structure.graph.Vertex;
+import com.baidu.hugegraph.structure.schema.IndexLabel;
+import com.baidu.hugegraph.testutil.Assert;
+import com.baidu.hugegraph.testutil.Utils;
+import com.baidu.hugegraph.util.DateUtil;
+import com.baidu.hugegraph.util.VersionUtil;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+public class IndexLabelApiTest extends BaseApiTest {
+
+    private static Function<String, IndexLabel> fillIndexLabel =
+            (name) -> schema().indexLabel(name)
+                              .onV("person")
+                              .by("age")
+                              .range()
+                              .build();
+
+    @BeforeClass
+    public static void prepareSchema() {
+        BaseApiTest.initPropertyKey();
+        BaseApiTest.initVertexLabel();
+        BaseApiTest.initEdgeLabel();
+    }
+
+    @Override
+    @After
+    public void teardown() throws Exception {
+        List<Long> taskIds = new ArrayList<>();
+        indexLabelAPI.list().forEach(il -> {
+            taskIds.add(indexLabelAPI.delete(il.name()));
+        });
+        taskIds.forEach(taskId -> waitUntilTaskCompleted(taskId));
+    }
+
+    @Test
+    public void testCreate() {
+        IndexLabel indexLabel = indexLabelAPI.create(
+                                fillIndexLabel.apply("personByAge"))
+                                .indexLabel();
+
+        Assert.assertEquals("personByAge", indexLabel.name());
+        Assert.assertEquals(HugeType.VERTEX_LABEL, indexLabel.baseType());
+        Assert.assertEquals("person", indexLabel.baseValue());
+        Assert.assertEquals(IndexType.RANGE, indexLabel.indexType());
+        List<String> fields = ImmutableList.of("age");
+        Assert.assertTrue(fields.size() == indexLabel.indexFields().size());
+        Assert.assertTrue(fields.containsAll(indexLabel.indexFields()));
+    }
+
+    @Test
+    public void testCreateWithIndexType() {
+        IndexLabel indexLabel = schema().indexLabel("personByAge")
+                                        .onV("person")
+                                        .by("age")
+                                        .indexType(IndexType.RANGE)
+                                        .build();
+
+        Assert.assertEquals("personByAge", indexLabel.name());
+        Assert.assertEquals(HugeType.VERTEX_LABEL, indexLabel.baseType());
+        Assert.assertEquals("person", indexLabel.baseValue());
+        Assert.assertEquals(IndexType.RANGE, indexLabel.indexType());
+        List<String> fields = ImmutableList.of("age");
+        Assert.assertTrue(fields.size() == indexLabel.indexFields().size());
+        Assert.assertTrue(fields.containsAll(indexLabel.indexFields()));
+    }
+
+    @Test
+    public void testCreateWithInvalidName() {
+        Utils.assertResponseError(400, () -> {
+            indexLabelAPI.create(fillIndexLabel.apply(""));
+        });
+        Utils.assertResponseError(400, () -> {
+            indexLabelAPI.create(fillIndexLabel.apply(" "));
+        });
+        Utils.assertResponseError(400, () -> {
+            indexLabelAPI.create(fillIndexLabel.apply("    "));
+        });
+    }
+
+    @Test
+    public void testCreateExistedIndexLabel() {
+        indexLabelAPI.create(fillIndexLabel.apply("personByAge"));
+
+        Utils.assertResponseError(400, () -> {
+            indexLabelAPI.create(fillIndexLabel.apply("personByAge"));
+        });
+    }
+
+    @Test
+    public void testCreateOnUndefinedSchemaLabel() {
+        IndexLabel indexLabel1 = schema().indexLabel("authorByAge")
+                                         .onV("author")
+                                         .by("age")
+                                         .range()
+                                         .build();
+        Utils.assertResponseError(400, () -> {
+            indexLabelAPI.create(indexLabel1);
+        });
+
+        IndexLabel indexLabel2 = schema().indexLabel("writeByDate")
+                                         .onE("write")
+                                         .by("date")
+                                         .secondary()
+                                         .build();
+        Utils.assertResponseError(400, () -> {
+            indexLabelAPI.create(indexLabel2);
+        });
+    }
+
+    @Test
+    public void testCreateSearchIndexOnMultiProperties() {
+        IndexLabel indexLabel = schema().indexLabel("personByAgeAndCity")
+                                        .onV("person")
+                                        .by("age", "city")
+                                        .search()
+                                        .build();
+        Utils.assertResponseError(400, () -> {
+            indexLabelAPI.create(indexLabel);
+        });
+    }
+
+    @Test
+    public void testCreateSecondaryIndexOnMultiPropertiesOverrideExist() {
+        String personByCity = "personByCity";
+        String personByCityAndAge = "personByCityAndAge";
+        schema().indexLabel("personByCity")
+                .onV("person")
+                .by("city")
+                .secondary()
+                .create();
+        indexLabelAPI.get(personByCity);
+        Assert.assertThrows(ServerException.class, () -> {
+            indexLabelAPI.get(personByCityAndAge);
+        }, e -> {
+            String expect = String.format("index label with name '%s' does " +
+                                          "not exist", personByCityAndAge);
+            Assert.assertContains(expect, e.getMessage());
+        });
+        schema().indexLabel(personByCityAndAge)
+                .onV("person")
+                .by("city", "age")
+                .secondary()
+                .create();
+        Assert.assertThrows(ServerException.class, () -> {
+            indexLabelAPI.get(personByCity);
+        }, e -> {
+            String expect = String.format("index label with name '%s' does " +
+                                          "not exist", personByCity);
+            Assert.assertContains(expect, e.getMessage());
+        });
+        indexLabelAPI.get(personByCityAndAge);
+    }
+
+    @Test
+    public void testCreateShardIndexOnMultiPropertiesOverrideExist() {
+        String personByCity = "personByCity";
+        String personByCityAndAge = "personByCityAndAge";
+        schema().indexLabel(personByCity)
+                .onV("person")
+                .by("city")
+                .secondary()
+                .create();
+        indexLabelAPI.get(personByCity);
+        Assert.assertThrows(ServerException.class, () -> {
+            indexLabelAPI.get(personByCityAndAge);
+        }, e -> {
+            String expect = String.format("index label with name '%s' does " +
+                                          "not exist", personByCityAndAge);
+            Assert.assertContains(expect, e.getMessage());
+        });
+        schema().indexLabel(personByCityAndAge)
+                .onV("person")
+                .by("city", "age")
+                .shard()
+                .create();
+        Assert.assertThrows(ServerException.class, () -> {
+            indexLabelAPI.get(personByCity);
+        }, e -> {
+            String expect = String.format("index label with name '%s' does " +
+                                          "not exist", personByCity);
+            Assert.assertContains(expect, e.getMessage());
+        });
+        indexLabelAPI.get(personByCityAndAge);
+    }
+
+    @Test
+    public void testCreateRangeIndexOnNotNumberProperty() {
+        IndexLabel indexLabel = schema().indexLabel("personByCity")
+                                        .onV("person")
+                                        .by("city")
+                                        .range()
+                                        .build();
+        Utils.assertResponseError(400, () -> {
+            indexLabelAPI.create(indexLabel);
+        });
+    }
+
+    @Test
+    public void testCreateUniqueIndex() {
+        IndexLabel indexLabel = schema().indexLabel("personByCity")
+                                        .onV("person")
+                                        .by("city")
+                                        .unique()
+                                        .create();
+        Assert.assertEquals("personByCity", indexLabel.name());
+        Assert.assertEquals(HugeType.VERTEX_LABEL, indexLabel.baseType());
+        Assert.assertEquals("person", indexLabel.baseValue());
+        Assert.assertEquals(IndexType.UNIQUE, indexLabel.indexType());
+        List<String> fields = ImmutableList.of("city");
+        Assert.assertTrue(fields.size() == indexLabel.indexFields().size());
+        Assert.assertTrue(fields.containsAll(indexLabel.indexFields()));
+    }
+
+    @Test
+    public void testGet() {
+        IndexLabel indexLabel1 = schema().indexLabel("personByAge")
+                                         .onV("person")
+                                         .by("age")
+                                         .range()
+                                         .build();
+
+        indexLabel1 = indexLabelAPI.create(indexLabel1).indexLabel();
+
+        IndexLabel indexLabel2 = indexLabelAPI.get("personByAge");
+
+        Assert.assertEquals(indexLabel1.name(), indexLabel2.name());
+        Assert.assertEquals(indexLabel1.baseType(), indexLabel2.baseType());
+        Assert.assertEquals(indexLabel1.baseValue(), indexLabel2.baseValue());
+        Assert.assertEquals(indexLabel1.indexType(), indexLabel2.indexType());
+        Assert.assertEquals(indexLabel1.indexFields(),
+                            indexLabel2.indexFields());
+    }
+
+    @Test
+    public void testGetNotExist() {
+        Utils.assertResponseError(404, () -> {
+            indexLabelAPI.get("not-exist-il");
+        });
+    }
+
+    @Test
+    public void testList() {
+        IndexLabel indexLabel1 = indexLabelAPI.create(
+                                 fillIndexLabel.apply("personByAge"))
+                                 .indexLabel();
+
+        IndexLabel indexLabel2 = schema().indexLabel("personByCity")
+                                         .onV("person")
+                                         .by("city")
+                                         .secondary()
+                                         .build();
+        indexLabel2 = indexLabelAPI.create(indexLabel2).indexLabel();
+
+        List<IndexLabel> indexLabels = indexLabelAPI.list();
+        Assert.assertEquals(2, indexLabels.size());
+        assertContains(indexLabels, indexLabel1);
+        assertContains(indexLabels, indexLabel2);
+    }
+
+    @Test
+    public void testListByNames() {
+        IndexLabel personByAge = fillIndexLabel.apply("personByAge");
+        personByAge = indexLabelAPI.create(personByAge).indexLabel();
+
+        IndexLabel personByCity = schema().indexLabel("personByCity")
+                                          .onV("person")
+                                          .by("city")
+                                          .secondary()
+                                          .build();
+        personByCity = indexLabelAPI.create(personByCity).indexLabel();
+
+        List<IndexLabel> indexLabels;
+
+        indexLabels = indexLabelAPI.list(ImmutableList.of("personByAge"));
+        Assert.assertEquals(1, indexLabels.size());
+        assertContains(indexLabels, personByAge);
+
+        indexLabels = indexLabelAPI.list(ImmutableList.of("personByCity"));
+        Assert.assertEquals(1, indexLabels.size());
+        assertContains(indexLabels, personByCity);
+
+        indexLabels = indexLabelAPI.list(ImmutableList.of("personByAge",
+                                                          "personByCity"));
+        Assert.assertEquals(2, indexLabels.size());
+        assertContains(indexLabels, personByAge);
+        assertContains(indexLabels, personByCity);
+    }
+
+    @Test
+    public void testDelete() {
+        String name = "personByAge";
+        indexLabelAPI.create(fillIndexLabel.apply(name));
+
+        long taskId = indexLabelAPI.delete(name);
+        waitUntilTaskCompleted(taskId);
+
+        Assert.assertThrows(ServerException.class, () -> {
+            indexLabelAPI.get(name);
+        }, e -> {
+            String expect = String.format("index label with name '%s' does " +
+                                          "not exist", name);
+            Assert.assertContains(expect, e.getMessage());
+        });
+    }
+
+    @Test
+    public void testDeleteNotExist() {
+        Utils.assertResponseError(404, () -> {
+            indexLabelAPI.delete("not-exist-il");
+        });
+    }
+
+    @Test
+    public void testAddIndexLabelWithUserData() {
+        IndexLabel personByAge = schema().indexLabel("personByAge")
+                                         .onV("person")
+                                         .by("age")
+                                         .range()
+                                         .userdata("min", 0)
+                                         .userdata("max", 100)
+                                         .build();
+        personByAge = indexLabelAPI.create(personByAge).indexLabel();
+        Assert.assertEquals(3, personByAge.userdata().size());
+        Assert.assertEquals(0, personByAge.userdata().get("min"));
+        Assert.assertEquals(100, personByAge.userdata().get("max"));
+        String time = (String) personByAge.userdata().get("~create_time");
+        Date createTime = DateUtil.parse(time);
+        Assert.assertTrue(createTime.before(DateUtil.now()));
+
+        IndexLabel personByCity = schema().indexLabel("personByCity")
+                                          .onV("person")
+                                          .by("city")
+                                          .secondary()
+                                          .userdata("length", 15)
+                                          .userdata("length", 18)
+                                          .build();
+        personByCity = indexLabelAPI.create(personByCity).indexLabel();
+        // The same key user data will be overwritten
+        Assert.assertEquals(2, personByCity.userdata().size());
+        Assert.assertEquals(18, personByCity.userdata().get("length"));
+        time = (String) personByCity.userdata().get("~create_time");
+        createTime = DateUtil.parse(time);
+        Assert.assertTrue(createTime.before(DateUtil.now()));
+
+        IndexLabel bookByName = schema().indexLabel("bookByName")
+                                        .onV("book")
+                                        .by("name")
+                                        .secondary()
+                                        .userdata("option",
+                                                  ImmutableList.of("xx", "yy"))
+                                        .build();
+        bookByName = indexLabelAPI.create(bookByName).indexLabel();
+        Assert.assertEquals(2, bookByName.userdata().size());
+        Assert.assertEquals(ImmutableList.of("xx", "yy"),
+                            bookByName.userdata().get("option"));
+        time = (String) bookByName.userdata().get("~create_time");
+        createTime = DateUtil.parse(time);
+        Assert.assertTrue(createTime.before(DateUtil.now()));
+    }
+
+    @Test
+    public void testAddIndexLabelWithUserDataWithApiVersion49() {
+        VersionUtil.Version originApiVersion = client().apiVersion();
+        VersionUtil.Version apiVersion = VersionUtil.Version.of("0.49");
+        client().apiVersion(apiVersion);
+
+        IndexLabel personByAge = schema().indexLabel("personByAge")
+                                         .onV("person")
+                                         .by("age")
+                                         .range()
+                                         .build();
+        personByAge = indexLabelAPI.create(personByAge).indexLabel();
+        Assert.assertEquals(1, personByAge.userdata().size());
+
+        IndexLabel personByAge1 = schema().indexLabel("personByAge1")
+                                          .onV("person")
+                                          .by("age")
+                                          .range()
+                                          .userdata("min", 0)
+                                          .userdata("max", 100)
+                                          .build();
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            indexLabelAPI.create(personByAge1).indexLabel();
+        }, (e) -> {
+            String msg = "Not support userdata of index label until api " +
+                         "version 0.50";
+            Assert.assertContains(msg, e.getMessage());
+        });
+
+        IndexLabel personByAge2 = schema().indexLabel("personByAge2")
+                                          .onV("person")
+                                          .by("age")
+                                          .userdata("min", 0)
+                                          .build();
+        Assert.assertThrows(NotSupportException.class, () -> {
+            indexLabelAPI.append(personByAge2);
+        }, (e) -> {
+            Assert.assertContains("action append on index label",
+                                  e.getMessage());
+        });
+
+        IndexLabel personByAge3 = schema().indexLabel("personByAge3")
+                                          .onV("person")
+                                          .by("age")
+                                          .userdata("min", 0)
+                                          .build();
+        Assert.assertThrows(NotSupportException.class, () -> {
+            indexLabelAPI.eliminate(personByAge3);
+        }, (e) -> {
+            Assert.assertContains("action eliminate on index label",
+                                  e.getMessage());
+        });
+
+        client().apiVersion(originApiVersion);
+    }
+
+    @Test
+    public void testAddIndexLabelWithRebuildFalse() {
+        Vertex vertex = new Vertex("person");
+        vertex.property("name", "James");
+        vertex.property("city", "Beijing");
+        vertex.property("age", 19);
+
+        vertex = vertexAPI.create(vertex);
+
+        IndexLabel personByAge = schema().indexLabel("personByAge")
+                                         .onV("person")
+                                         .by("city")
+                                         .secondary()
+                                         .rebuild(false)
+                                         .build();
+        IndexLabel.IndexLabelWithTask il = indexLabelAPI.create(personByAge);
+        IndexLabel created = il.indexLabel();
+        Assert.assertEquals(personByAge.name(), created.name());
+        Assert.assertEquals(personByAge.baseType(), created.baseType());
+        Assert.assertEquals(personByAge.baseValue(), created.baseValue());
+        Assert.assertEquals(personByAge.indexType(), created.indexType());
+        Assert.assertEquals(Task.TASK_ID_NULL, il.taskId());
+
+        Map<String, Object> properties = ImmutableMap.of("city", "Beijing");
+        List<Vertex> vertices = vertexAPI.list("person", properties, 0,
+                                               null, 10).results();
+        Assert.assertEquals(0, vertices.size());
+
+        long taskId = rebuildAPI.rebuild(personByAge);
+        waitUntilTaskCompleted(taskId);
+
+        vertices = vertexAPI.list("person", properties, 0, null, 10)
+                            .results();
+        Assert.assertEquals(1, vertices.size());
+        Assert.assertEquals(vertex, vertices.get(0));
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/api/JobApiTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/JobApiTest.java
new file mode 100644
index 0000000..d00a561
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/JobApiTest.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api;
+
+import org.junit.After;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.baidu.hugegraph.structure.Task;
+import com.baidu.hugegraph.structure.schema.EdgeLabel;
+import com.baidu.hugegraph.structure.schema.IndexLabel;
+import com.baidu.hugegraph.structure.schema.VertexLabel;
+import com.baidu.hugegraph.testutil.Assert;
+
+public class JobApiTest extends BaseApiTest {
+
+    @BeforeClass
+    public static void prepareSchema() {
+        BaseApiTest.initPropertyKey();
+        BaseApiTest.initVertexLabel();
+        BaseApiTest.initEdgeLabel();
+        BaseApiTest.initIndexLabel();
+    }
+
+    @After
+    public void teardown() throws Exception {
+        taskAPI.list(null, -1).forEach(task -> taskAPI.delete(task.id()));
+    }
+
+    @Test
+    public void testRebuildVertexLabel() {
+        VertexLabel person = schema().getVertexLabel("person");
+        long taskId = rebuildAPI.rebuild(person);
+        Task task = taskAPI.get(taskId);
+        Assert.assertNotNull(task);
+        Assert.assertEquals(taskId, task.id());
+        waitUntilTaskCompleted(taskId);
+    }
+
+    @Test
+    public void testRebuildEdgeLabel() {
+        EdgeLabel created = schema().getEdgeLabel("created");
+        long taskId = rebuildAPI.rebuild(created);
+        Task task = taskAPI.get(taskId);
+        Assert.assertNotNull(task);
+        Assert.assertEquals(taskId, task.id());
+        waitUntilTaskCompleted(taskId);
+    }
+
+    @Test
+    public void testRebuildIndexLabel() {
+        IndexLabel personByCity = schema().getIndexLabel("personByAge");
+        long taskId = rebuildAPI.rebuild(personByCity);
+        Task task = taskAPI.get(taskId);
+        Assert.assertNotNull(task);
+        Assert.assertEquals(taskId, task.id());
+        waitUntilTaskCompleted(taskId);
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/api/PropertyKeyApiTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/PropertyKeyApiTest.java
new file mode 100644
index 0000000..17d158f
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/PropertyKeyApiTest.java
@@ -0,0 +1,510 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import org.junit.After;
+import org.junit.Test;
+
+import com.baidu.hugegraph.structure.constant.AggregateType;
+import com.baidu.hugegraph.structure.constant.Cardinality;
+import com.baidu.hugegraph.structure.constant.DataType;
+import com.baidu.hugegraph.structure.constant.WriteType;
+import com.baidu.hugegraph.structure.schema.PropertyKey;
+import com.baidu.hugegraph.testutil.Assert;
+import com.baidu.hugegraph.testutil.Utils;
+import com.baidu.hugegraph.util.DateUtil;
+import com.google.common.collect.ImmutableList;
+
+public class PropertyKeyApiTest extends BaseApiTest {
+
+    @After
+    public void teardown() throws Exception {
+        List<Long> pkTaskIds = new ArrayList<>();
+        propertyKeyAPI.list().forEach(propertyKey -> {
+            pkTaskIds.add(propertyKeyAPI.delete(propertyKey.name()));
+        });
+        pkTaskIds.forEach(taskId -> waitUntilTaskCompleted(taskId));
+    }
+
+    @Test
+    public void testCreate() {
+        PropertyKey propertyKey = schema().propertyKey("name")
+                                          .asText()
+                                          .valueSingle()
+                                          .build();
+
+        PropertyKey.PropertyKeyWithTask propertyKeyWithTask;
+        propertyKeyWithTask = propertyKeyAPI.create(propertyKey);
+        Assert.assertEquals(0L, propertyKeyWithTask.taskId());
+        propertyKey = propertyKeyWithTask.propertyKey();
+
+        Assert.assertEquals("name", propertyKey.name());
+        Assert.assertEquals(DataType.TEXT, propertyKey.dataType());
+        Assert.assertEquals(Cardinality.SINGLE, propertyKey.cardinality());
+    }
+
+    @Test
+    public void testCreateWithDataType() {
+        PropertyKey propertyKey = schema().propertyKey("name")
+                                          .dataType(DataType.LONG)
+                                          .valueSingle()
+                                          .build();
+
+        PropertyKey.PropertyKeyWithTask propertyKeyWithTask;
+        propertyKeyWithTask = propertyKeyAPI.create(propertyKey);
+        Assert.assertEquals(0L, propertyKeyWithTask.taskId());
+        propertyKey = propertyKeyWithTask.propertyKey();
+
+        Assert.assertEquals("name", propertyKey.name());
+        Assert.assertEquals(DataType.LONG, propertyKey.dataType());
+        Assert.assertEquals(Cardinality.SINGLE, propertyKey.cardinality());
+    }
+
+    @Test
+    public void testCreateWithCardinality() {
+        PropertyKey propertyKey = schema().propertyKey("name")
+                                          .asText()
+                                          .cardinality(Cardinality.SET)
+                                          .build();
+
+        PropertyKey.PropertyKeyWithTask propertyKeyWithTask;
+        propertyKeyWithTask = propertyKeyAPI.create(propertyKey);
+        Assert.assertEquals(0L, propertyKeyWithTask.taskId());
+        propertyKey = propertyKeyWithTask.propertyKey();
+
+        Assert.assertEquals("name", propertyKey.name());
+        Assert.assertEquals(DataType.TEXT, propertyKey.dataType());
+        Assert.assertEquals(Cardinality.SET, propertyKey.cardinality());
+    }
+
+    @Test
+    public void testCreateWithAggregateType() {
+        PropertyKey propertyKey = schema().propertyKey("name")
+                                          .asText().valueSingle()
+                                          .build();
+
+        PropertyKey.PropertyKeyWithTask propertyKeyWithTask;
+        propertyKeyWithTask = propertyKeyAPI.create(propertyKey);
+        Assert.assertEquals(0L, propertyKeyWithTask.taskId());
+        propertyKey = propertyKeyWithTask.propertyKey();
+
+        Assert.assertEquals("name", propertyKey.name());
+        Assert.assertEquals(DataType.TEXT, propertyKey.dataType());
+        Assert.assertEquals(Cardinality.SINGLE, propertyKey.cardinality());
+        Assert.assertEquals(AggregateType.NONE, propertyKey.aggregateType());
+        Assert.assertTrue(propertyKey.aggregateType().isNone());
+        Assert.assertFalse(propertyKey.aggregateType().isNumber());
+        Assert.assertTrue(propertyKey.aggregateType().isIndexable());
+
+        propertyKey = schema().propertyKey("no")
+                              .asText().valueSingle()
+                              .calcOld()
+                              .build();
+
+        propertyKeyWithTask = propertyKeyAPI.create(propertyKey);
+        Assert.assertEquals(0L, propertyKeyWithTask.taskId());
+        propertyKey = propertyKeyWithTask.propertyKey();
+
+        Assert.assertEquals("no", propertyKey.name());
+        Assert.assertEquals(DataType.TEXT, propertyKey.dataType());
+        Assert.assertEquals(Cardinality.SINGLE, propertyKey.cardinality());
+        Assert.assertEquals(AggregateType.OLD, propertyKey.aggregateType());
+        Assert.assertTrue(propertyKey.aggregateType().isOld());
+        Assert.assertTrue(propertyKey.aggregateType().isIndexable());
+        Assert.assertFalse(propertyKey.aggregateType().isNumber());
+
+        propertyKey = schema().propertyKey("max")
+                              .asInt().valueSingle()
+                              .calcMax()
+                              .build();
+
+        propertyKeyWithTask = propertyKeyAPI.create(propertyKey);
+        Assert.assertEquals(0L, propertyKeyWithTask.taskId());
+        propertyKey = propertyKeyWithTask.propertyKey();
+
+        Assert.assertEquals("max", propertyKey.name());
+        Assert.assertEquals(DataType.INT, propertyKey.dataType());
+        Assert.assertEquals(Cardinality.SINGLE, propertyKey.cardinality());
+        Assert.assertEquals(AggregateType.MAX, propertyKey.aggregateType());
+        Assert.assertTrue(propertyKey.aggregateType().isMax());
+        Assert.assertTrue(propertyKey.aggregateType().isIndexable());
+        Assert.assertTrue(propertyKey.aggregateType().isNumber());
+
+        propertyKey = schema().propertyKey("min")
+                              .asInt().valueSingle()
+                              .calcMin()
+                              .build();
+
+        propertyKeyWithTask = propertyKeyAPI.create(propertyKey);
+        Assert.assertEquals(0L, propertyKeyWithTask.taskId());
+        propertyKey = propertyKeyWithTask.propertyKey();
+
+        Assert.assertEquals("min", propertyKey.name());
+        Assert.assertEquals(DataType.INT, propertyKey.dataType());
+        Assert.assertEquals(Cardinality.SINGLE, propertyKey.cardinality());
+        Assert.assertEquals(AggregateType.MIN, propertyKey.aggregateType());
+        Assert.assertTrue(propertyKey.aggregateType().isMin());
+        Assert.assertTrue(propertyKey.aggregateType().isIndexable());
+        Assert.assertTrue(propertyKey.aggregateType().isNumber());
+
+        propertyKey = schema().propertyKey("sum")
+                              .asInt().valueSingle()
+                              .calcSum()
+                              .build();
+
+        propertyKeyWithTask = propertyKeyAPI.create(propertyKey);
+        Assert.assertEquals(0L, propertyKeyWithTask.taskId());
+        propertyKey = propertyKeyWithTask.propertyKey();
+
+        Assert.assertEquals("sum", propertyKey.name());
+        Assert.assertEquals(DataType.INT, propertyKey.dataType());
+        Assert.assertEquals(Cardinality.SINGLE, propertyKey.cardinality());
+        Assert.assertEquals(AggregateType.SUM, propertyKey.aggregateType());
+        Assert.assertTrue(propertyKey.aggregateType().isSum());
+        Assert.assertFalse(propertyKey.aggregateType().isIndexable());
+        Assert.assertTrue(propertyKey.aggregateType().isNumber());
+
+        propertyKey = schema().propertyKey("total")
+                              .asInt().valueSingle()
+                              .aggregateType(AggregateType.SUM)
+                              .build();
+
+        propertyKeyWithTask = propertyKeyAPI.create(propertyKey);
+        Assert.assertEquals(0L, propertyKeyWithTask.taskId());
+        propertyKey = propertyKeyWithTask.propertyKey();
+
+        Assert.assertEquals("total", propertyKey.name());
+        Assert.assertEquals(DataType.INT, propertyKey.dataType());
+        Assert.assertEquals(Cardinality.SINGLE, propertyKey.cardinality());
+        Assert.assertEquals(AggregateType.SUM, propertyKey.aggregateType());
+        Assert.assertTrue(propertyKey.aggregateType().isSum());
+        Assert.assertFalse(propertyKey.aggregateType().isIndexable());
+        Assert.assertTrue(propertyKey.aggregateType().isNumber());
+
+        propertyKey = schema().propertyKey("nameV46")
+                              .asText().valueSingle()
+                              .build();
+        PropertyKey.PropertyKeyV46 pk = propertyKey.switchV46();
+        Assert.assertEquals("nameV46", pk.name());
+        Assert.assertEquals(DataType.TEXT, pk.dataType());
+        Assert.assertEquals(Cardinality.SINGLE, pk.cardinality());
+
+        propertyKey = schema().propertyKey("nameV58")
+                              .asText().valueSingle()
+                              .calcOld()
+                              .build();
+        PropertyKey.PropertyKeyV58 pk1 = propertyKey.switchV58();
+        Assert.assertEquals("nameV58", pk1.name());
+        Assert.assertEquals(DataType.TEXT, pk1.dataType());
+        Assert.assertEquals(Cardinality.SINGLE, pk1.cardinality());
+        Assert.assertEquals(AggregateType.OLD, pk1.aggregateType());
+    }
+
+    @Test
+    public void testCreateWithInvalidName() {
+        Utils.assertResponseError(400, () -> {
+            propertyKeyAPI.create(new PropertyKey(""));
+        });
+        Utils.assertResponseError(400, () -> {
+            propertyKeyAPI.create(new PropertyKey(" "));
+        });
+        Utils.assertResponseError(400, () -> {
+            propertyKeyAPI.create(new PropertyKey("    "));
+        });
+    }
+
+    @Test
+    public void testCreateExistedPropertyKey() {
+        PropertyKey propertyKey = new PropertyKey("name");
+        propertyKeyAPI.create(propertyKey);
+
+        Utils.assertResponseError(400, () -> {
+            propertyKeyAPI.create(new PropertyKey("name"));
+        });
+    }
+
+    @Test
+    public void testGet() {
+        PropertyKey propertyKey1 = schema().propertyKey("name")
+                                           .asText()
+                                           .valueSingle()
+                                           .build();
+
+        PropertyKey.PropertyKeyWithTask propertyKeyWithTask;
+        propertyKeyWithTask = propertyKeyAPI.create(propertyKey1);
+        Assert.assertEquals(0L, propertyKeyWithTask.taskId());
+        propertyKey1 = propertyKeyWithTask.propertyKey();
+
+        PropertyKey propertyKey2 = propertyKeyAPI.get("name");
+
+        Assert.assertEquals(propertyKey1.name(), propertyKey2.name());
+        Assert.assertEquals(propertyKey1.dataType(), propertyKey2.dataType());
+        Assert.assertEquals(propertyKey1.cardinality(),
+                            propertyKey2.cardinality());
+    }
+
+    @Test
+    public void testGetNotExist() {
+        Utils.assertResponseError(404, () -> {
+            propertyKeyAPI.get("not-exist-pk");
+        });
+    }
+
+    @Test
+    public void testList() {
+        PropertyKey propertyKey1 = schema().propertyKey("name")
+                                           .asText()
+                                           .valueSingle()
+                                           .build();
+        PropertyKey.PropertyKeyWithTask propertyKeyWithTask;
+        propertyKeyWithTask = propertyKeyAPI.create(propertyKey1);
+        Assert.assertEquals(0L, propertyKeyWithTask.taskId());
+        propertyKey1 = propertyKeyWithTask.propertyKey();
+
+        PropertyKey propertyKey2 = schema().propertyKey("age")
+                                           .asInt()
+                                           .valueSingle()
+                                           .build();
+        propertyKeyWithTask = propertyKeyAPI.create(propertyKey2);
+        Assert.assertEquals(0L, propertyKeyWithTask.taskId());
+        propertyKey2 = propertyKeyWithTask.propertyKey();
+
+        List<PropertyKey> propertyKeys = propertyKeyAPI.list();
+        Assert.assertEquals(2, propertyKeys.size());
+        assertContains(propertyKeys, propertyKey1);
+        assertContains(propertyKeys, propertyKey2);
+    }
+
+    @Test
+    public void testListByNames() {
+        PropertyKey name = schema().propertyKey("name").asText().build();
+        PropertyKey.PropertyKeyWithTask propertyKeyWithTask;
+        propertyKeyWithTask = propertyKeyAPI.create(name);
+        Assert.assertEquals(0L, propertyKeyWithTask.taskId());
+        name = propertyKeyWithTask.propertyKey();
+
+        PropertyKey age = schema().propertyKey("age").asInt().build();
+        propertyKeyWithTask = propertyKeyAPI.create(age);
+        Assert.assertEquals(0L, propertyKeyWithTask.taskId());
+        age = propertyKeyWithTask.propertyKey();
+
+        List<PropertyKey> propertyKeys;
+
+        propertyKeys = propertyKeyAPI.list(ImmutableList.of("name"));
+        Assert.assertEquals(1, propertyKeys.size());
+        assertContains(propertyKeys, name);
+
+        propertyKeys = propertyKeyAPI.list(ImmutableList.of("age"));
+        Assert.assertEquals(1, propertyKeys.size());
+        assertContains(propertyKeys, age);
+
+        propertyKeys = propertyKeyAPI.list(ImmutableList.of("name", "age"));
+        Assert.assertEquals(2, propertyKeys.size());
+        assertContains(propertyKeys, name);
+        assertContains(propertyKeys, age);
+    }
+
+    @Test
+    public void testDelete() {
+        PropertyKey propertyKey = schema().propertyKey("name")
+                                          .asText()
+                                          .valueSingle()
+                                          .build();
+        propertyKeyAPI.create(propertyKey);
+        propertyKeyAPI.delete("name");
+
+        Utils.assertResponseError(404, () -> {
+            propertyKeyAPI.get("name");
+        });
+    }
+
+    @Test
+    public void testDeleteNotExist() {
+        Utils.assertResponseError(404, () -> {
+            propertyKeyAPI.delete("not-exist-pk");
+        });
+    }
+
+    @Test
+    public void testAddPropertyKeyWithUserData() {
+        PropertyKey age = schema().propertyKey("age")
+                                  .userdata("min", 0)
+                                  .userdata("max", 100)
+                                  .build();
+        PropertyKey.PropertyKeyWithTask propertyKeyWithTask;
+        propertyKeyWithTask = propertyKeyAPI.create(age);
+        Assert.assertEquals(0L, propertyKeyWithTask.taskId());
+        age = propertyKeyWithTask.propertyKey();
+        Assert.assertEquals(3, age.userdata().size());
+        Assert.assertEquals(0, age.userdata().get("min"));
+        Assert.assertEquals(100, age.userdata().get("max"));
+        String time = (String) age.userdata().get("~create_time");
+        Date createTime = DateUtil.parse(time);
+        Assert.assertTrue(createTime.before(DateUtil.now()));
+
+        PropertyKey id = schema().propertyKey("id")
+                                 .userdata("length", 15)
+                                 .userdata("length", 18)
+                                 .build();
+        propertyKeyWithTask = propertyKeyAPI.create(id);
+        Assert.assertEquals(0L, propertyKeyWithTask.taskId());
+        id = propertyKeyWithTask.propertyKey();
+        // The same key user data will be overwritten
+        Assert.assertEquals(2, id.userdata().size());
+        Assert.assertEquals(18, id.userdata().get("length"));
+        time = (String) id.userdata().get("~create_time");
+        createTime = DateUtil.parse(time);
+        Assert.assertTrue(createTime.before(DateUtil.now()));
+
+        PropertyKey sex = schema().propertyKey("sex")
+                                  .userdata("range",
+                                            ImmutableList.of("male", "female"))
+                                  .build();
+        propertyKeyWithTask = propertyKeyAPI.create(sex);
+        Assert.assertEquals(0L, propertyKeyWithTask.taskId());
+        sex = propertyKeyWithTask.propertyKey();
+        Assert.assertEquals(2, sex.userdata().size());
+        Assert.assertEquals(ImmutableList.of("male", "female"),
+                            sex.userdata().get("range"));
+        time = (String) sex.userdata().get("~create_time");
+        createTime = DateUtil.parse(time);
+        Assert.assertTrue(createTime.before(DateUtil.now()));
+    }
+
+    @Test
+    public void testAddOlapPropertyKey() {
+        PropertyKey pagerank = schema().propertyKey("pagerank")
+                                       .asDouble()
+                                       .writeType(WriteType.OLAP_RANGE)
+                                       .build();
+
+        PropertyKey.PropertyKeyWithTask propertyKeyWithTask;
+        propertyKeyWithTask = propertyKeyAPI.create(pagerank);
+        long taskId = propertyKeyWithTask.taskId();
+        Assert.assertNotEquals(0L, taskId);
+        waitUntilTaskCompleted(taskId);
+        pagerank = propertyKeyWithTask.propertyKey();
+
+        Assert.assertEquals("pagerank", pagerank.name());
+        Assert.assertEquals(WriteType.OLAP_RANGE, pagerank.writeType());
+        Assert.assertEquals(DataType.DOUBLE, pagerank.dataType());
+
+        PropertyKey wcc = schema().propertyKey("wcc")
+                                  .asText()
+                                  .writeType(WriteType.OLAP_SECONDARY)
+                                  .build();
+
+        propertyKeyWithTask = propertyKeyAPI.create(wcc);
+        taskId = propertyKeyWithTask.taskId();
+        Assert.assertNotEquals(0L, taskId);
+        waitUntilTaskCompleted(taskId);
+        wcc = propertyKeyWithTask.propertyKey();
+
+        Assert.assertEquals("wcc", wcc.name());
+        Assert.assertEquals(WriteType.OLAP_SECONDARY, wcc.writeType());
+        Assert.assertEquals(DataType.TEXT, wcc.dataType());
+
+        PropertyKey none = schema().propertyKey("none")
+                                   .asText()
+                                   .writeType(WriteType.OLAP_COMMON)
+                                   .build();
+
+        propertyKeyWithTask = propertyKeyAPI.create(none);
+        taskId = propertyKeyWithTask.taskId();
+        Assert.assertNotEquals(0L, taskId);
+        waitUntilTaskCompleted(taskId);
+        none = propertyKeyWithTask.propertyKey();
+
+        Assert.assertEquals("none", none.name());
+        Assert.assertEquals(WriteType.OLAP_COMMON, none.writeType());
+        Assert.assertEquals(DataType.TEXT, none.dataType());
+    }
+
+    @Test
+    public void testClearOlapPropertyKey() {
+        PropertyKey pagerank = schema().propertyKey("pagerank")
+                                       .asDouble()
+                                       .writeType(WriteType.OLAP_RANGE)
+                                       .build();
+
+        PropertyKey.PropertyKeyWithTask propertyKeyWithTask;
+        propertyKeyWithTask = propertyKeyAPI.create(pagerank);
+        long taskId = propertyKeyWithTask.taskId();
+        Assert.assertNotEquals(0L, taskId);
+        waitUntilTaskCompleted(taskId);
+        pagerank = propertyKeyWithTask.propertyKey();
+
+        Assert.assertEquals("pagerank", pagerank.name());
+        Assert.assertEquals(WriteType.OLAP_RANGE, pagerank.writeType());
+        Assert.assertEquals(DataType.DOUBLE, pagerank.dataType());
+
+        propertyKeyWithTask = propertyKeyAPI.clear(pagerank);
+        taskId = propertyKeyWithTask.taskId();
+        Assert.assertNotEquals(0L, taskId);
+        waitUntilTaskCompleted(taskId);
+        pagerank = propertyKeyWithTask.propertyKey();
+
+        Assert.assertEquals("pagerank", pagerank.name());
+        Assert.assertEquals(WriteType.OLAP_RANGE, pagerank.writeType());
+        Assert.assertEquals(DataType.DOUBLE, pagerank.dataType());
+
+        pagerank = propertyKeyAPI.get("pagerank");
+
+        Assert.assertEquals("pagerank", pagerank.name());
+        Assert.assertEquals(WriteType.OLAP_RANGE, pagerank.writeType());
+        Assert.assertEquals(DataType.DOUBLE, pagerank.dataType());
+    }
+
+    @Test
+    public void testDeleteOlapPropertyKey() {
+        PropertyKey pagerank = schema().propertyKey("pagerank")
+                                       .asDouble()
+                                       .writeType(WriteType.OLAP_RANGE)
+                                       .build();
+
+        PropertyKey.PropertyKeyWithTask propertyKeyWithTask;
+        propertyKeyWithTask = propertyKeyAPI.create(pagerank);
+        long taskId = propertyKeyWithTask.taskId();
+        Assert.assertNotEquals(0L, taskId);
+        waitUntilTaskCompleted(taskId);
+        pagerank = propertyKeyWithTask.propertyKey();
+
+        Assert.assertEquals("pagerank", pagerank.name());
+        Assert.assertEquals(WriteType.OLAP_RANGE, pagerank.writeType());
+        Assert.assertEquals(DataType.DOUBLE, pagerank.dataType());
+
+        taskId = propertyKeyAPI.delete(pagerank.name());
+        Assert.assertNotEquals(0L, taskId);
+        waitUntilTaskCompleted(taskId);
+        pagerank = propertyKeyWithTask.propertyKey();
+
+        Assert.assertEquals("pagerank", pagerank.name());
+        Assert.assertEquals(WriteType.OLAP_RANGE, pagerank.writeType());
+        Assert.assertEquals(DataType.DOUBLE, pagerank.dataType());
+
+        Utils.assertResponseError(404, () -> {
+            propertyKeyAPI.get("pagerank");
+        });
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/api/RestoreApiTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/RestoreApiTest.java
new file mode 100644
index 0000000..50e8145
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/RestoreApiTest.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.baidu.hugegraph.structure.constant.GraphMode;
+import com.baidu.hugegraph.testutil.Assert;
+
+public class RestoreApiTest extends BaseApiTest {
+
+    private static final String GRAPH = "hugegraph";
+
+    @Before
+    public void initGraphModeNone() {
+        graphsAPI.mode(GRAPH, GraphMode.NONE);
+    }
+
+    @Test
+    public void testGetGraphMode() {
+        GraphMode mode = graphsAPI.mode(GRAPH);
+        Assert.assertEquals(GraphMode.NONE, mode);
+    }
+
+    @Test
+    public void testSetGraphMode() {
+        GraphMode mode = graphsAPI.mode(GRAPH);
+        Assert.assertEquals(GraphMode.NONE, mode);
+
+        graphsAPI.mode(GRAPH, GraphMode.RESTORING);
+        mode = graphsAPI.mode(GRAPH);
+        Assert.assertEquals(GraphMode.RESTORING, mode);
+
+        graphsAPI.mode(GRAPH, GraphMode.MERGING);
+        mode = graphsAPI.mode(GRAPH);
+        Assert.assertEquals(GraphMode.MERGING, mode);
+
+        graphsAPI.mode(GRAPH, GraphMode.NONE);
+        mode = graphsAPI.mode(GRAPH);
+        Assert.assertEquals(GraphMode.NONE, mode);
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/api/SchemaApiTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/SchemaApiTest.java
new file mode 100644
index 0000000..7a9ea69
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/SchemaApiTest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api;
+
+import java.util.List;
+import java.util.Map;
+
+import org.junit.Test;
+
+import com.baidu.hugegraph.structure.SchemaElement;
+import com.baidu.hugegraph.testutil.Assert;
+
+public class SchemaApiTest extends BaseApiTest {
+
+    @Test
+    public void testlist() {
+        BaseApiTest.initPropertyKey();
+        BaseApiTest.initVertexLabel();
+        BaseApiTest.initEdgeLabel();
+
+        Map<String, List<SchemaElement>> schemas = schemaAPI.list();
+
+        Assert.assertEquals(4, schemas.size());
+        Assert.assertTrue(schemas.containsKey("propertykeys"));
+        Assert.assertTrue(schemas.containsKey("vertexlabels"));
+        Assert.assertTrue(schemas.containsKey("edgelabels"));
+        Assert.assertTrue(schemas.containsKey("indexlabels"));
+        Assert.assertEquals(7, schemas.get("propertykeys").size());
+        Assert.assertEquals(3, schemas.get("vertexlabels").size());
+        Assert.assertEquals(2, schemas.get("edgelabels").size());
+        Assert.assertTrue(schemas.get("indexlabels").isEmpty());
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/api/TaskApiTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/TaskApiTest.java
new file mode 100644
index 0000000..60c1dec
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/TaskApiTest.java
@@ -0,0 +1,293 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.junit.After;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.baidu.hugegraph.api.gremlin.GremlinRequest;
+import com.baidu.hugegraph.api.task.TasksWithPage;
+import com.baidu.hugegraph.structure.Task;
+import com.baidu.hugegraph.structure.gremlin.ResultSet;
+import com.baidu.hugegraph.structure.schema.IndexLabel;
+import com.baidu.hugegraph.testutil.Assert;
+import com.baidu.hugegraph.testutil.Utils;
+
+public class TaskApiTest extends BaseApiTest {
+
+    @BeforeClass
+    public static void prepareSchema() {
+        BaseApiTest.initPropertyKey();
+        BaseApiTest.initVertexLabel();
+        BaseApiTest.initEdgeLabel();
+        BaseApiTest.initIndexLabel();
+    }
+
+    @After
+    public void teardown() throws Exception {
+        taskAPI.list(null, -1).forEach(task -> taskAPI.delete(task.id()));
+    }
+
+    @Test
+    public void testListAll() {
+        IndexLabel personByCity = schema().getIndexLabel("personByCity");
+        IndexLabel personByAge = schema().getIndexLabel("personByAge");
+        IndexLabel knowsByDate = schema().getIndexLabel("knowsByDate");
+        IndexLabel createdByDate = schema().getIndexLabel("createdByDate");
+
+        Set<Long> taskIds = new HashSet<>();
+        taskIds.add(rebuildAPI.rebuild(personByCity));
+        taskIds.add(rebuildAPI.rebuild(personByAge));
+        taskIds.add(rebuildAPI.rebuild(knowsByDate));
+        taskIds.add(rebuildAPI.rebuild(createdByDate));
+
+        List<Task> tasks = taskAPI.list(null, -1);
+
+        Assert.assertEquals(4, tasks.size());
+
+        Set<Long> listedTaskIds = new HashSet<>();
+        for (Task task : tasks) {
+            listedTaskIds.add(task.id());
+        }
+        Assert.assertEquals(taskIds.size(), listedTaskIds.size());
+        Assert.assertTrue(taskIds.containsAll(listedTaskIds));
+
+        taskIds.forEach(BaseApiTest::waitUntilTaskCompleted);
+
+        taskIds.forEach(id -> taskAPI.delete(id));
+
+        tasks = taskAPI.list(null, -1);
+        Assert.assertEquals(0, tasks.size());
+    }
+
+    @Test
+    public void testListByIds() {
+        IndexLabel personByCity = schema().getIndexLabel("personByCity");
+        IndexLabel personByAge = schema().getIndexLabel("personByAge");
+        IndexLabel knowsByDate = schema().getIndexLabel("knowsByDate");
+        IndexLabel createdByDate = schema().getIndexLabel("createdByDate");
+
+        List<Long> taskIds = new ArrayList<>();
+        taskIds.add(rebuildAPI.rebuild(personByCity));
+        taskIds.add(rebuildAPI.rebuild(personByAge));
+        taskIds.add(rebuildAPI.rebuild(knowsByDate));
+        taskIds.add(rebuildAPI.rebuild(createdByDate));
+
+        taskIds.forEach(BaseApiTest::waitUntilTaskCompleted);
+
+        List<Task> tasks = taskAPI.list(taskIds);
+
+        Assert.assertEquals(4, tasks.size());
+        Set<Long> listedTaskIds = new HashSet<>();
+        for (Task task : tasks) {
+            listedTaskIds.add(task.id());
+        }
+        Assert.assertEquals(taskIds.size(), listedTaskIds.size());
+        Assert.assertTrue(taskIds.containsAll(listedTaskIds));
+    }
+
+    @Test
+    public void testListByStatus() {
+        IndexLabel personByCity = schema().getIndexLabel("personByCity");
+        IndexLabel personByAge = schema().getIndexLabel("personByAge");
+        IndexLabel knowsByDate = schema().getIndexLabel("knowsByDate");
+        IndexLabel createdByDate = schema().getIndexLabel("createdByDate");
+
+        Set<Long> taskIds = new HashSet<>();
+        taskIds.add(rebuildAPI.rebuild(personByCity));
+        taskIds.add(rebuildAPI.rebuild(personByAge));
+        taskIds.add(rebuildAPI.rebuild(knowsByDate));
+        taskIds.add(rebuildAPI.rebuild(createdByDate));
+
+        taskIds.forEach(BaseApiTest::waitUntilTaskCompleted);
+
+        List<Task> tasks = taskAPI.list("SUCCESS", -1);
+
+        Assert.assertEquals(4, tasks.size());
+        Set<Long> listedTaskIds = new HashSet<>();
+        for (Task task : tasks) {
+            listedTaskIds.add(task.id());
+        }
+        Assert.assertEquals(taskIds.size(), listedTaskIds.size());
+        Assert.assertTrue(taskIds.containsAll(listedTaskIds));
+
+        tasks = taskAPI.list("SUCCESS", 3);
+
+        Assert.assertEquals(3, tasks.size());
+        Set<Long> listedTaskIds1 = new HashSet<>();
+        for (Task task : tasks) {
+            listedTaskIds1.add(task.id());
+        }
+        Assert.assertEquals(3, listedTaskIds1.size());
+        Assert.assertTrue(taskIds.containsAll(listedTaskIds));
+    }
+
+    @Test
+    public void testListByStatusAndPage() {
+        IndexLabel personByCity = schema().getIndexLabel("personByCity");
+        IndexLabel personByAge = schema().getIndexLabel("personByAge");
+        IndexLabel knowsByDate = schema().getIndexLabel("knowsByDate");
+        IndexLabel createdByDate = schema().getIndexLabel("createdByDate");
+
+        Set<Long> taskIds = new HashSet<>();
+        taskIds.add(rebuildAPI.rebuild(personByCity));
+        taskIds.add(rebuildAPI.rebuild(personByAge));
+        taskIds.add(rebuildAPI.rebuild(knowsByDate));
+        taskIds.add(rebuildAPI.rebuild(createdByDate));
+
+        taskIds.forEach(BaseApiTest::waitUntilTaskCompleted);
+
+        TasksWithPage tasksWithPage = taskAPI.list("SUCCESS", "", 2);
+
+        List<Task> tasks = tasksWithPage.tasks();
+        Assert.assertEquals(2, tasks.size());
+        Set<Long> listedTaskIds = new HashSet<>();
+        for (Task task : tasks) {
+            listedTaskIds.add(task.id());
+        }
+
+        tasksWithPage = taskAPI.list("SUCCESS", tasksWithPage.page(), 2);
+
+        List<Task> tasks1 = tasksWithPage.tasks();
+        Assert.assertEquals(2, tasks1.size());
+        Assert.assertNull(tasksWithPage.page());
+        for (Task task : tasks1) {
+            listedTaskIds.add(task.id());
+        }
+        Assert.assertEquals(taskIds.size(), listedTaskIds.size());
+        Assert.assertTrue(taskIds.containsAll(listedTaskIds));
+    }
+
+    @Test
+    public void testGet() {
+        IndexLabel personByCity = schema().getIndexLabel("personByCity");
+        long taskId = rebuildAPI.rebuild(personByCity);
+
+        Task task = taskAPI.get(taskId);
+
+        Assert.assertNotNull(task);
+        Assert.assertEquals(taskId, task.id());
+        waitUntilTaskCompleted(taskId);
+    }
+
+    @Test
+    public void testDelete() {
+        IndexLabel personByCity = schema().getIndexLabel("personByCity");
+        long taskId = rebuildAPI.rebuild(personByCity);
+
+        waitUntilTaskCompleted(taskId);
+        taskAPI.delete(taskId);
+
+        Utils.assertResponseError(404, () -> {
+            taskAPI.get(taskId);
+        });
+    }
+
+    @Test
+    public void testCancel() {
+        schema().vertexLabel("man").useAutomaticId().ifNotExist().create();
+
+        String groovy = "for (int i = 0; i < 10; i++) {" +
+                            "hugegraph.addVertex(T.label, 'man');" +
+                            "hugegraph.tx().commit();" +
+                        "}";
+        // Insert 10 records in sync mode
+        GremlinRequest request = new GremlinRequest(groovy);
+        gremlin().execute(request);
+        // Verify insertion takes effect
+        groovy = "g.V()";
+        request = new GremlinRequest(groovy);
+        ResultSet resultSet = gremlin().execute(request);
+        Assert.assertEquals(10, resultSet.size());
+        // Delete to prepare for insertion in async mode
+        groovy = "g.V().drop()";
+        request = new GremlinRequest(groovy);
+        gremlin().execute(request);
+
+        /*
+         * The asyn task scripts need to be able to handle interrupts,
+         * otherwise they cannot be cancelled
+         */
+        groovy = "for (int i = 0; i < 10; i++) {" +
+                     "hugegraph.addVertex(T.label, 'man');" +
+                     "hugegraph.tx().commit();" +
+                     "try {" +
+                         "sleep(1000);" +
+                     "} catch (InterruptedException e) {" +
+                         "break;" +
+                     "}" +
+                 "}";
+        request = new GremlinRequest(groovy);
+        long taskId = gremlin().executeAsTask(request);
+
+        groovy = "g.V()";
+        request = new GremlinRequest(groovy);
+        // Wait async task running
+        while (true) {
+            resultSet = gremlin().execute(request);
+            if (resultSet.size() > 0) {
+                break;
+            } else {
+                try {
+                    Thread.sleep(1000);
+                } catch (InterruptedException ignored) {}
+            }
+        }
+        // Cancel async task
+        Task task = taskAPI.cancel(taskId);
+        Assert.assertTrue(task.cancelling());
+
+        try {
+            Thread.sleep(1000L);
+        } catch (InterruptedException e) {
+            // ignored
+        }
+
+        task = taskAPI.get(taskId);
+        Assert.assertTrue(task.cancelled());
+
+        resultSet = gremlin().execute(request);
+        Assert.assertTrue(resultSet.size() < 10);
+    }
+
+    @Test
+    public void testTaskAsMap() {
+        IndexLabel personByCity = schema().getIndexLabel("personByCity");
+        long taskId = rebuildAPI.rebuild(personByCity);
+
+        Task task = taskAPI.get(taskId);
+
+        Assert.assertNotNull(task);
+        Assert.assertEquals(taskId, task.id());
+        waitUntilTaskCompleted(taskId);
+
+        task = taskAPI.get(taskId);
+        Map<String, Object> taskMap = task.asMap();
+        Assert.assertEquals("rebuild_index", taskMap.get(Task.P.TYPE));
+        Assert.assertEquals("success", taskMap.get(Task.P.STATUS));
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/api/VariablesApiTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/VariablesApiTest.java
new file mode 100644
index 0000000..4228689
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/VariablesApiTest.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.baidu.hugegraph.testutil.Assert;
+import com.baidu.hugegraph.testutil.Utils;
+
+public class VariablesApiTest extends BaseApiTest {
+
+    @Override
+    @Before
+    public void setup() {
+        clearVariables();
+    }
+
+    @Override
+    @After
+    public void teardown() {
+        clearVariables();
+    }
+
+    public void clearVariables() {
+        variablesAPI.all().keySet().forEach(variableKey -> {
+            variablesAPI.remove(variableKey);
+        });
+    }
+
+    @Test
+    public void testSetWithStringValue() {
+        variablesAPI.set("version", "0.3.2");
+        Map<String, Object> variable = variablesAPI.get("version");
+        assertContains(variable, "version", "0.3.2");
+
+        variablesAPI.set("graph number", 10);
+        variable = variablesAPI.get("graph number");
+        assertContains(variable, "graph number", 10);
+
+        variablesAPI.set("graph weight", 1.0);
+        variable = variablesAPI.get("graph weight");
+        assertContains(variable, "graph weight", 1.0);
+
+        List<String> sList = Arrays.asList("first", "second", "third");
+        variablesAPI.set("graph list", sList);
+        variable = variablesAPI.get("graph list");
+        assertContains(variable, "graph list", sList);
+
+        Set<Integer> iSet = new HashSet<>();
+        iSet.add(1);
+        iSet.add(2);
+        iSet.add(3);
+        variablesAPI.set("graph set", iSet);
+        variable = variablesAPI.get("graph set");
+        assertContains(variable, "graph set", iSet);
+    }
+
+    @Test
+    public void testSetTwice() {
+        variablesAPI.set("version", 3);
+        variablesAPI.set("version", "0.3.2");
+
+        Map<String, Object> variables = variablesAPI.all();
+        Assert.assertEquals(1, variables.size());
+    }
+
+    @Test
+    public void testAll() {
+        variablesAPI.set("version", "0.3.2");
+        variablesAPI.set("superUser", "Tom");
+        variablesAPI.set("userNumber", 10);
+
+        Map<String, Object> variables = variablesAPI.all();
+        Assert.assertEquals(3, variables.size());
+    }
+
+    @Test
+    public void testGet() {
+        variablesAPI.set("version", "0.3.2");
+        Map<String, Object> variable = variablesAPI.get("version");
+        assertContains(variable, "version", "0.3.2");
+    }
+
+    @Test
+    public void testGetNotExist() {
+        Utils.assertResponseError(404, () -> {
+            variablesAPI.get("not-exist-variable");
+        });
+    }
+
+    @Test
+    public void testRemove() {
+        variablesAPI.set("version", "0.3.2");
+        variablesAPI.remove("version");
+        Utils.assertResponseError(404, () -> {
+            variablesAPI.get("version");
+        });
+    }
+
+    @Test
+    public void testDeleteNotExist() {
+        Utils.assertResponseError(404, () -> {
+            vertexLabelAPI.delete("not-exist-variable");
+        });
+    }
+
+    private static void assertContains(Map<String, Object> variables,
+                                       String key, Object value) {
+        Assert.assertTrue(variables.containsKey(key));
+
+        if (variables.get(key) instanceof Collection) {
+            Assert.assertTrue(value instanceof Collection);
+            Collection<?> expect = (Collection<?>) value;
+            Collection<?> actual = (Collection<?>) variables.get(key);
+            Assert.assertTrue(expect.size()== actual.size());
+            actual.forEach(elem -> {
+                Assert.assertTrue((expect.contains(elem)));
+            });
+        } else {
+            Assert.assertTrue(variables.get(key).equals(value));
+        }
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/api/VertexApiTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/VertexApiTest.java
new file mode 100644
index 0000000..9f8fc9d
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/VertexApiTest.java
@@ -0,0 +1,679 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import org.junit.After;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.baidu.hugegraph.api.gremlin.GremlinRequest;
+import com.baidu.hugegraph.driver.SchemaManager;
+import com.baidu.hugegraph.exception.ServerException;
+import com.baidu.hugegraph.structure.constant.GraphReadMode;
+import com.baidu.hugegraph.structure.constant.T;
+import com.baidu.hugegraph.structure.constant.WriteType;
+import com.baidu.hugegraph.structure.graph.Vertex;
+import com.baidu.hugegraph.structure.gremlin.ResultSet;
+import com.baidu.hugegraph.structure.schema.PropertyKey;
+import com.baidu.hugegraph.testutil.Assert;
+import com.baidu.hugegraph.testutil.Utils;
+import com.baidu.hugegraph.util.DateUtil;
+import com.google.common.collect.ImmutableMap;
+
+public class VertexApiTest extends BaseApiTest {
+
+    @BeforeClass
+    public static void prepareSchema() {
+        BaseApiTest.initPropertyKey();
+        BaseApiTest.initVertexLabel();
+        BaseApiTest.initEdgeLabel();
+    }
+
+    @Override
+    @After
+    public void teardown() {
+        vertexAPI.list(-1).results().forEach(v -> vertexAPI.delete(v.id()));
+    }
+
+    @Test
+    public void testCreate() {
+        Vertex vertex = new Vertex("person");
+        vertex.property("name", "James");
+        vertex.property("city", "Beijing");
+        vertex.property("age", 19);
+
+        vertex = vertexAPI.create(vertex);
+
+        Assert.assertEquals("person", vertex.label());
+        Map<String, Object> props = ImmutableMap.of("name", "James",
+                                                    "city", "Beijing",
+                                                    "age", 19);
+        Assert.assertEquals(props, vertex.properties());
+    }
+
+    @Test
+    public void testCreateWithUndefinedLabel() {
+        Vertex vertex = new Vertex("undefined");
+        vertex.property("name", "James");
+        vertex.property("city", "Beijing");
+        vertex.property("age", 19);
+
+        Utils.assertResponseError(400, () -> {
+            vertexAPI.create(vertex);
+        });
+    }
+
+    @Test
+    public void testCreateWithUndefinedProperty() {
+        Vertex vertex = new Vertex("person");
+        vertex.property("name", "James");
+        vertex.property("not-exist-key", "not-exist-value");
+
+        Utils.assertResponseError(400, () -> {
+            vertexAPI.create(vertex);
+        });
+    }
+
+    @Test
+    public void testCreateWithoutPrimaryKey() {
+        Vertex vertex = new Vertex("person");
+        vertex.property("city", "Beijing");
+        vertex.property("age", 19);
+
+        Utils.assertResponseError(400, () -> {
+            vertexAPI.create(vertex);
+        });
+    }
+
+    @Test
+    public void testCreateWithCustomizeStringId() {
+        Vertex person = new Vertex("person");
+        person.property(T.id, "123456");
+        person.property("name", "James");
+        person.property("city", "Beijing");
+        person.property("age", 19);
+
+        Utils.assertResponseError(400, () -> {
+            vertexAPI.create(person);
+        });
+
+        Vertex book = new Vertex("book");
+        book.id("ISBN-123456");
+        book.property("name", "spark graphx");
+
+        Vertex vertex = vertexAPI.create(book);
+        Assert.assertEquals("book", vertex.label());
+        Assert.assertEquals("ISBN-123456", vertex.id());
+        Map<String, Object> props = ImmutableMap.of("name", "spark graphx");
+        Assert.assertEquals(props, vertex.properties());
+    }
+
+    @Test
+    public void testCreateWithCustomizeNumberId() {
+        Vertex person = new Vertex("person");
+        person.property(T.id, 123456);
+        person.property("name", "James");
+        person.property("city", "Beijing");
+        person.property("age", 19);
+
+        Utils.assertResponseError(400, () -> {
+            vertexAPI.create(person);
+        });
+
+        schema().vertexLabel("log")
+                .useCustomizeNumberId()
+                .properties("date")
+                .ifNotExist()
+                .create();
+
+        Vertex log = new Vertex("log");
+        log.id(123456);
+        log.property("date", "2018-01-01");
+
+        Vertex vertex = vertexAPI.create(log);
+        Assert.assertEquals("log", vertex.label());
+        Assert.assertEquals(123456, vertex.id());
+        String date = Utils.formatDate("2018-01-01");
+        Map<String, Object> props = ImmutableMap.of("date", date);
+        Assert.assertEquals(props, vertex.properties());
+    }
+
+    @Test
+    public void testCreateWithCustomizeUuidId() {
+        schema().vertexLabel("user")
+                .useCustomizeUuidId()
+                .properties("date")
+                .ifNotExist()
+                .create();
+
+        Vertex log = new Vertex("user");
+        log.id("835e1153-9281-4957-8691-cf79258e90eb");
+        log.property("date", "2018-01-01");
+
+        Vertex vertex = vertexAPI.create(log);
+        Assert.assertEquals("user", vertex.label());
+        Assert.assertEquals("835e1153-9281-4957-8691-cf79258e90eb",
+                            vertex.id());
+        String date = Utils.formatDate("2018-01-01");
+        Map<String, Object> props = ImmutableMap.of("date", date);
+        Assert.assertEquals(props, vertex.properties());
+
+        // NOTE: must clean here due to type info
+        UUID id = UUID.fromString("835e1153-9281-4957-8691-cf79258e90eb");
+        vertexAPI.delete(id);
+    }
+
+    @Test
+    public void testCreateWithNullableKeysAbsent() {
+        Vertex vertex = new Vertex("person");
+        // Absent prop city
+        vertex.property("name", "James");
+        vertex.property("age", 19);
+
+        vertex = vertexAPI.create(vertex);
+
+        Assert.assertEquals("person", vertex.label());
+        Map<String, Object> props = ImmutableMap.of("name", "James",
+                                                    "age", 19);
+        Assert.assertEquals(props, vertex.properties());
+    }
+
+    @Test
+    public void testCreateWithNonNullKeysAbsent() {
+        Vertex vertex = new Vertex("person");
+        // Absent prop 'age'
+        vertex.property("name", "James");
+        vertex.property("city", "Beijing");
+
+        Utils.assertResponseError(400, () -> {
+            vertexAPI.create(vertex);
+        });
+    }
+
+    @Test
+    public void testCreateExistVertex() {
+        Vertex vertex = new Vertex("person");
+        vertex.property("name", "James");
+        vertex.property("city", "Beijing");
+        vertex.property("age", 19);
+        vertexAPI.create(vertex);
+
+        vertex = new Vertex("person");
+        vertex.property("name", "James");
+        vertex.property("city", "Shanghai");
+        vertex.property("age", 20);
+        vertex = vertexAPI.create(vertex);
+
+        Assert.assertEquals("person", vertex.label());
+        Map<String, Object> props = ImmutableMap.of("name", "James",
+                                                    "city", "Shanghai",
+                                                    "age", 20);
+        Assert.assertEquals(props, vertex.properties());
+    }
+
+    @Test
+    public void testCreateVertexWithTtl() {
+        SchemaManager schema = schema();
+        schema.vertexLabel("fan")
+              .properties("name", "age", "city")
+              .primaryKeys("name")
+              .ttl(3000L)
+              .ifNotExist()
+              .create();
+
+        Vertex vertex = graph().addVertex(T.label, "fan", "name", "Baby",
+                                          "age", 3, "city", "Beijing");
+
+        Vertex result = graph().getVertex(vertex.id());
+        Assert.assertEquals("fan", result.label());
+        Map<String, Object> props = ImmutableMap.of("name", "Baby",
+                                                    "city", "Beijing",
+                                                    "age", 3);
+        Assert.assertEquals(props, result.properties());
+
+        try {
+            Thread.sleep(1100L);
+        } catch (InterruptedException e) {
+            // Ignore
+        }
+
+        result = graph().getVertex(vertex.id());
+        Assert.assertEquals("fan", result.label());
+        Assert.assertEquals(props, result.properties());
+
+        try {
+            Thread.sleep(1100L);
+        } catch (InterruptedException e) {
+            // Ignore
+        }
+
+        result = graph().getVertex(vertex.id());
+        Assert.assertEquals("fan", result.label());
+        Assert.assertEquals(props, result.properties());
+
+        try {
+            Thread.sleep(1100L);
+        } catch (InterruptedException e) {
+            // Ignore
+        }
+
+        Assert.assertThrows(ServerException.class, () -> {
+            graph().getVertex(vertex.id());
+        }, e -> {
+            Assert.assertContains("does not exist", e.getMessage());
+        });
+    }
+
+    @Test
+    public void testCreateVertexWithTtlAndTtlStartTime() {
+        SchemaManager schema = schema();
+        schema.vertexLabel("follower")
+              .properties("name", "age", "city", "date")
+              .primaryKeys("name")
+              .ttl(3000L)
+              .ttlStartTime("date")
+              .ifNotExist()
+              .create();
+        long date = DateUtil.now().getTime() - 1000L;
+        String dateString = Utils.formatDate(new Date(date));
+        Vertex vertex = graph().addVertex(T.label, "follower", "name", "Baby",
+                                          "age", 3, "city", "Beijing",
+                                          "date", date);
+
+        Vertex result = graph().getVertex(vertex.id());
+        Assert.assertEquals("follower", result.label());
+        Map<String, Object> props = ImmutableMap.of("name", "Baby",
+                                                    "city", "Beijing",
+                                                    "age", 3,
+                                                    "date", dateString);
+        Assert.assertEquals(props, result.properties());
+
+        try {
+            Thread.sleep(1100L);
+        } catch (InterruptedException e) {
+            // Ignore
+        }
+
+        result = graph().getVertex(vertex.id());
+        Assert.assertEquals("follower", result.label());
+        Assert.assertEquals(props, result.properties());
+
+        try {
+            Thread.sleep(1100L);
+        } catch (InterruptedException e) {
+            // Ignore
+        }
+
+        Assert.assertThrows(ServerException.class, () -> {
+            graph().getVertex(vertex.id());
+        }, e -> {
+            Assert.assertContains("does not exist", e.getMessage());
+        });
+    }
+
+    @Test
+    public void testOlapPropertyWrite() {
+        List<Vertex> vertices = super.create100PersonBatch();
+        List<Object> ids = vertexAPI.create(vertices);
+
+        // Create olap property key
+        PropertyKey pagerank = schema().propertyKey("pagerank")
+                                       .asDouble()
+                                       .writeType(WriteType.OLAP_RANGE)
+                                       .build();
+
+        PropertyKey.PropertyKeyWithTask propertyKeyWithTask;
+        propertyKeyWithTask = propertyKeyAPI.create(pagerank);
+        long taskId1 = propertyKeyWithTask.taskId();
+        Assert.assertNotEquals(0L, taskId1);
+
+        PropertyKey wcc = schema().propertyKey("wcc")
+                                  .asText()
+                                  .writeType(WriteType.OLAP_SECONDARY)
+                                  .build();
+
+        propertyKeyWithTask = propertyKeyAPI.create(wcc);
+        long taskId2 = propertyKeyWithTask.taskId();
+        Assert.assertNotEquals(0L, taskId2);
+
+        PropertyKey none = schema().propertyKey("none")
+                                   .asText()
+                                   .writeType(WriteType.OLAP_COMMON)
+                                   .build();
+
+        propertyKeyWithTask = propertyKeyAPI.create(none);
+        long taskId3 = propertyKeyWithTask.taskId();
+        Assert.assertNotEquals(0L, taskId3);
+
+        waitUntilTaskCompleted(taskId1);
+        waitUntilTaskCompleted(taskId2);
+        waitUntilTaskCompleted(taskId3);
+
+        // Add olap properties
+        vertices = new ArrayList<>(100);
+        for (int i = 0; i < 100; i++) {
+            Vertex vertex = new Vertex(null);
+            vertex.id(ids.get(i));
+            vertex.property("pagerank", 0.1D * i);
+            vertices.add(vertex);
+        }
+
+        ids = vertexAPI.create(vertices);
+        Assert.assertEquals(100, ids.size());
+
+        vertices = new ArrayList<>(100);
+        for (int i = 0; i < 100; i++) {
+            Vertex vertex = new Vertex(null);
+            vertex.id(ids.get(i));
+            vertex.property("wcc", "wcc" + i);
+            vertices.add(vertex);
+        }
+
+        ids = vertexAPI.create(vertices);
+        Assert.assertEquals(100, ids.size());
+
+        vertices = new ArrayList<>(100);
+        for (int i = 0; i < 100; i++) {
+            Vertex vertex = new Vertex(null);
+            vertex.id(ids.get(i));
+            vertex.property("none", "none" + i);
+            vertices.add(vertex);
+        }
+
+        ids = vertexAPI.create(vertices);
+        Assert.assertEquals(100, ids.size());
+
+        // Query vertices by id before set graph read mode to 'ALL'
+        for (int i = 0; i < 100; i++) {
+            Vertex person = vertexAPI.get(ids.get(i));
+            Assert.assertEquals("person", person.label());
+            Map<String, Object> props = ImmutableMap.of("name", "Person-" + i,
+                                                        "city", "Beijing",
+                                                        "age", 30);
+            Assert.assertEquals(props, person.properties());
+        }
+
+        // Set graph read mode to 'ALL'
+        graphsAPI.readMode("hugegraph", GraphReadMode.ALL);
+
+        // Query vertices by id after set graph read mode to 'ALL'
+        for (int i = 0; i < 100; i++) {
+            Vertex person = vertexAPI.get(ids.get(i));
+            Assert.assertEquals("person", person.label());
+            Map<String, Object> props = ImmutableMap.<String, Object>builder()
+                                                    .put("name", "Person-" + i)
+                                                    .put("city", "Beijing")
+                                                    .put("age", 30)
+                                                    .put("pagerank", 0.1D * i)
+                                                    .put("wcc", "wcc" + i)
+                                                    .put("none", "none" + i)
+                                                    .build();
+            Assert.assertEquals(props, person.properties());
+        }
+
+        // Query vertices by olap properties
+        GremlinRequest request = new GremlinRequest(
+                                 "g.V().has(\"pagerank\", P.gte(5))");
+        ResultSet resultSet = gremlin().execute(request);
+        Assert.assertEquals(50, resultSet.size());
+
+        request = new GremlinRequest(
+                  "g.V().has(\"wcc\", P.within(\"wcc10\", \"wcc20\"))");
+        resultSet = gremlin().execute(request);
+        Assert.assertEquals(2, resultSet.size());
+
+        // Clear olap property key
+        propertyKeyWithTask = propertyKeyAPI.clear(pagerank);
+        taskId1 = propertyKeyWithTask.taskId();
+        Assert.assertNotEquals(0L, taskId1);
+
+        propertyKeyWithTask = propertyKeyAPI.clear(wcc);
+        taskId2 = propertyKeyWithTask.taskId();
+        Assert.assertNotEquals(0L, taskId2);
+
+        propertyKeyWithTask = propertyKeyAPI.clear(none);
+        taskId3 = propertyKeyWithTask.taskId();
+        Assert.assertNotEquals(0L, taskId3);
+
+        waitUntilTaskCompleted(taskId1);
+        waitUntilTaskCompleted(taskId2);
+        waitUntilTaskCompleted(taskId3);
+
+        // Query after clear olap property key
+        request = new GremlinRequest("g.V().has(\"pagerank\", P.gte(5))");
+        resultSet = gremlin().execute(request);
+        Assert.assertEquals(0, resultSet.size());
+
+        request = new GremlinRequest(
+                  "g.V().has(\"wcc\", P.within(\"wcc10\", \"wcc20\"))");
+        resultSet = gremlin().execute(request);
+        Assert.assertEquals(0, resultSet.size());
+
+        // Delete olap property key
+        taskId1 = propertyKeyAPI.delete(pagerank.name());
+        Assert.assertNotEquals(0L, taskId1);
+
+        taskId2 = propertyKeyAPI.delete(wcc.name());
+        Assert.assertNotEquals(0L, taskId2);
+
+        taskId3 = propertyKeyAPI.delete(none.name());
+        Assert.assertNotEquals(0L, taskId3);
+
+        waitUntilTaskCompleted(taskId1);
+        waitUntilTaskCompleted(taskId2);
+        waitUntilTaskCompleted(taskId3);
+
+        // Query after delete olap property key
+        Assert.assertThrows(ServerException.class, () -> {
+            gremlin().execute(new GremlinRequest(
+                              "g.V().has(\"pagerank\", P.gte(5))"));
+        }, e -> {
+            Assert.assertContains("Undefined property key: 'pagerank'",
+                                  e.getMessage());
+        });
+
+        Assert.assertThrows(ServerException.class, () -> {
+            gremlin().execute(new GremlinRequest(
+                      "g.V().has(\"wcc\", P.within(\"wcc10\", \"wcc20\"))"));
+        }, e -> {
+            Assert.assertContains("Undefined property key: 'wcc'",
+                                  e.getMessage());
+        });
+
+        // Resume graph read mode to 'OLTP_ONLY'
+        graphsAPI.readMode("hugegraph", GraphReadMode.OLTP_ONLY);
+    }
+
+    @Test
+    public void testBatchCreate() {
+        List<Vertex> vertices = super.create100PersonBatch();
+        vertexAPI.create(vertices);
+
+        List<Object> ids = vertexAPI.create(vertices);
+        Assert.assertEquals(100, ids.size());
+        for (int i = 0; i < 100; i++) {
+            Vertex person = vertexAPI.get(ids.get(i));
+            Assert.assertEquals("person", person.label());
+            Map<String, Object> props = ImmutableMap.of("name", "Person-" + i,
+                                                        "city", "Beijing",
+                                                        "age", 30);
+            Assert.assertEquals(props, person.properties());
+        }
+    }
+
+    @Test
+    public void testBatchCreateContainsInvalidVertex() {
+        List<Vertex> vertices = super.create100PersonBatch();
+        vertices.get(0).property("invalid-key", "invalid-value");
+        vertices.get(10).property("not-exist-key", "not-exist-value");
+
+        Utils.assertResponseError(400, () -> {
+            vertexAPI.create(vertices);
+        });
+    }
+
+    @Test
+    public void testBatchCreateWithMoreThanBatchSize() {
+        List<Vertex> vertices = new ArrayList<>(1000);
+        for (int i = 0; i < 1000; i++) {
+            Vertex vertex = new Vertex("person");
+            vertex.property("name", "Person" + "-" + i);
+            vertex.property("city", "Beijing");
+            vertex.property("age", 20);
+            vertices.add(vertex);
+        }
+        Utils.assertResponseError(400, () -> {
+            vertexAPI.create(vertices);
+        });
+    }
+
+    @Test
+    public void testGet() {
+        Vertex vertex1 = new Vertex("person");
+        vertex1.property("name", "James");
+        vertex1.property("city", "Beijing");
+        vertex1.property("age", 19);
+
+        vertex1 = vertexAPI.create(vertex1);
+
+        Vertex vertex2 = vertexAPI.get(vertex1.id());
+        Assert.assertEquals(vertex1.id(), vertex2.id());
+        Assert.assertEquals(vertex1.label(), vertex2.label());
+        Assert.assertEquals(vertex1.properties(), vertex2.properties());
+    }
+
+    @Test
+    public void testGetWithCustomizeStringId() {
+        Vertex vertex1 = new Vertex("book");
+        vertex1.id("ISBN-123456");
+        vertex1.property("name", "spark graphx");
+
+        vertex1 = vertexAPI.create(vertex1);
+
+        Vertex vertex2 = vertexAPI.get("ISBN-123456");
+        Assert.assertEquals(vertex1.id(), vertex2.id());
+        Assert.assertEquals(vertex1.label(), vertex2.label());
+        Assert.assertEquals(vertex1.properties(), vertex2.properties());
+    }
+
+    @Test
+    public void testGetWithCustomizeNumberId() {
+        schema().vertexLabel("log")
+                .useCustomizeNumberId()
+                .properties("date")
+                .ifNotExist()
+                .create();
+
+        Vertex vertex1 = new Vertex("log");
+        vertex1.id(123456);
+        vertex1.property("date", "2018-01-01");
+
+        vertex1 = vertexAPI.create(vertex1);
+
+        Vertex vertex2 = vertexAPI.get(123456);
+        Assert.assertEquals(vertex1.id(), vertex2.id());
+        Assert.assertEquals(vertex1.label(), vertex2.label());
+        Assert.assertEquals(vertex1.properties(), vertex2.properties());
+    }
+
+    @Test
+    public void testGetWithCustomizeUuidId() {
+        schema().vertexLabel("user")
+                .useCustomizeUuidId()
+                .properties("date")
+                .ifNotExist()
+                .create();
+
+        Vertex vertex1 = new Vertex("user");
+        vertex1.id("835e1153-9281-4957-8691-cf79258e90eb");
+        vertex1.property("date", "2018-01-01");
+
+        vertex1 = vertexAPI.create(vertex1);
+
+        UUID id = UUID.fromString("835e1153-9281-4957-8691-cf79258e90eb");
+        Vertex vertex2 = vertexAPI.get(id);
+        Assert.assertEquals(vertex1.id(), vertex2.id());
+        Assert.assertEquals(vertex1.label(), vertex2.label());
+        Assert.assertEquals(vertex1.properties(), vertex2.properties());
+
+        // NOTE: must clean here due to type info
+        vertexAPI.delete(id);
+    }
+
+    @Test
+    public void testGetNotExist() {
+        Utils.assertResponseError(404, () -> {
+            vertexAPI.get("not-exist-vertex-id");
+        });
+    }
+
+    @Test
+    public void testList() {
+        List<Vertex> vertices = super.create100PersonBatch();
+        vertexAPI.create(vertices);
+
+        vertices = vertexAPI.list(-1).results();
+        Assert.assertEquals(100, vertices.size());
+    }
+
+    @Test
+    public void testListWithLimit() {
+        List<Vertex> vertices = super.create100PersonBatch();
+        vertexAPI.create(vertices);
+
+        vertices = vertexAPI.list(10).results();
+        Assert.assertEquals(10, vertices.size());
+    }
+
+    @Test
+    public void testDelete() {
+        Vertex vertex = new Vertex("person");
+        vertex.property("name", "James");
+        vertex.property("city", "Beijing");
+        vertex.property("age", 19);
+
+        vertex = vertexAPI.create(vertex);
+
+        Object id = vertex.id();
+        vertexAPI.delete(id);
+
+        Utils.assertResponseError(404, () -> {
+            vertexAPI.get(id);
+        });
+    }
+
+    @Test
+    public void testDeleteNotExist() {
+        Utils.assertResponseError(400, () -> {
+            vertexAPI.delete("not-exist-v");
+        });
+    }
+
+    @SuppressWarnings("unused")
+    private static void assertContains(List<Vertex> vertices, Vertex vertex) {
+        Assert.assertTrue(Utils.contains(vertices, vertex));
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/api/VertexLabelApiTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/VertexLabelApiTest.java
new file mode 100644
index 0000000..5d86361
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/VertexLabelApiTest.java
@@ -0,0 +1,499 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+
+import org.junit.After;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.baidu.hugegraph.structure.constant.IdStrategy;
+import com.baidu.hugegraph.structure.schema.VertexLabel;
+import com.baidu.hugegraph.testutil.Assert;
+import com.baidu.hugegraph.testutil.Utils;
+import com.baidu.hugegraph.util.DateUtil;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+
+public class VertexLabelApiTest extends BaseApiTest {
+
+    @BeforeClass
+    public static void prepareSchema() {
+        BaseApiTest.initPropertyKey();
+    }
+
+    @Override
+    @After
+    public void teardown() throws Exception {
+        List<Long> taskIds = new ArrayList<>();
+        vertexLabelAPI.list().forEach(vl -> {
+            taskIds.add(vertexLabelAPI.delete(vl.name()));
+        });
+        taskIds.forEach(taskId -> waitUntilTaskCompleted(taskId));
+    }
+
+    @Test
+    public void testCreate() {
+        VertexLabel vertexLabel = schema().vertexLabel("person")
+                                          .useAutomaticId()
+                                          .properties("name", "age", "city")
+                                          .build();
+
+        vertexLabel = vertexLabelAPI.create(vertexLabel);
+
+        Assert.assertEquals("person", vertexLabel.name());
+        Assert.assertEquals(IdStrategy.AUTOMATIC, vertexLabel.idStrategy());
+        Assert.assertEquals(true, vertexLabel.enableLabelIndex());
+        Set<String> props = ImmutableSet.of("name", "age", "city");
+        Assert.assertEquals(props, vertexLabel.properties());
+    }
+
+    @Test
+    public void testCreateWithEnableLabelIndexFalse() {
+        VertexLabel vertexLabel = schema().vertexLabel("person")
+                                          .useAutomaticId()
+                                          .properties("name", "age", "city")
+                                          .enableLabelIndex(false)
+                                          .build();
+
+        vertexLabel = vertexLabelAPI.create(vertexLabel);
+
+        Assert.assertEquals("person", vertexLabel.name());
+        Assert.assertEquals(IdStrategy.AUTOMATIC, vertexLabel.idStrategy());
+        Assert.assertEquals(false, vertexLabel.enableLabelIndex());
+        Set<String> props = ImmutableSet.of("name", "age", "city");
+        Assert.assertEquals(props, vertexLabel.properties());
+    }
+
+    @Test
+    public void testCreateWithUuidIdStrategy() {
+        VertexLabel vertexLabel = schema().vertexLabel("person")
+                                          .useCustomizeUuidId()
+                                          .properties("name", "age", "city")
+                                          .build();
+
+        vertexLabel = vertexLabelAPI.create(vertexLabel);
+
+        Assert.assertEquals("person", vertexLabel.name());
+        Assert.assertEquals(IdStrategy.CUSTOMIZE_UUID, vertexLabel.idStrategy());
+        Assert.assertEquals(true, vertexLabel.enableLabelIndex());
+        Set<String> props = ImmutableSet.of("name", "age", "city");
+        Assert.assertEquals(props, vertexLabel.properties());
+
+        vertexLabel = schema().vertexLabel("person1")
+                              .idStrategy(IdStrategy.CUSTOMIZE_UUID)
+                              .properties("name", "age", "city")
+                              .build();
+
+        vertexLabel = vertexLabelAPI.create(vertexLabel);
+
+        Assert.assertEquals("person1", vertexLabel.name());
+        Assert.assertEquals(IdStrategy.CUSTOMIZE_UUID, vertexLabel.idStrategy());
+        Assert.assertEquals(true, vertexLabel.enableLabelIndex());
+        props = ImmutableSet.of("name", "age", "city");
+        Assert.assertEquals(props, vertexLabel.properties());
+    }
+
+    @Test
+    public void testCreateWithInvalidName() {
+        Utils.assertResponseError(400, () -> {
+            vertexLabelAPI.create(new VertexLabel(""));
+        });
+        Utils.assertResponseError(400, () -> {
+            vertexLabelAPI.create(new VertexLabel(" "));
+        });
+        Utils.assertResponseError(400, () -> {
+            vertexLabelAPI.create(new VertexLabel("    "));
+        });
+    }
+
+    @Test
+    public void testCreateExistedVertexLabel() {
+        VertexLabel vertexLabel = new VertexLabel("name");
+        vertexLabelAPI.create(vertexLabel);
+
+        Utils.assertResponseError(400, () -> {
+            vertexLabelAPI.create(new VertexLabel("name"));
+        });
+    }
+
+    @Test
+    public void testCreateWithUndefinedPropertyKey() {
+        VertexLabel vertexLabel = schema().vertexLabel("person")
+                                          .usePrimaryKeyId()
+                                          .properties("name", "undefined")
+                                          .primaryKeys("name")
+                                          .build();
+
+        Utils.assertResponseError(400, () -> {
+            vertexLabelAPI.create(vertexLabel);
+        });
+    }
+
+    @Test
+    public void testCreateWithUndefinedPrimaryKey() {
+        VertexLabel vertexLabel = schema().vertexLabel("person")
+                                          .usePrimaryKeyId()
+                                          .properties("name", "age", "city")
+                                          .primaryKeys("undefined")
+                                          .build();
+
+        Utils.assertResponseError(400, () -> {
+            vertexLabelAPI.create(vertexLabel);
+        });
+    }
+
+    @Test
+    public void testCreateWithUndefinedNullableKeys() {
+        VertexLabel vertexLabel = schema().vertexLabel("person")
+                                          .usePrimaryKeyId()
+                                          .properties("name", "age", "city")
+                                          .primaryKeys("name")
+                                          .nullableKeys("undefined")
+                                          .build();
+
+        Utils.assertResponseError(400, () -> {
+            vertexLabelAPI.create(vertexLabel);
+        });
+    }
+
+    @Test
+    public void testCreateWithNonNullKeysIntersectPrimaryKeys() {
+        VertexLabel vertexLabel1 = schema().vertexLabel("person")
+                                           .usePrimaryKeyId()
+                                           .properties("name", "age", "city")
+                                           .primaryKeys("name")
+                                           .nullableKeys("name")
+                                           .build();
+        Utils.assertResponseError(400, () -> {
+            vertexLabelAPI.create(vertexLabel1);
+        });
+
+        VertexLabel vertexLabel2 = schema().vertexLabel("person")
+                                           .usePrimaryKeyId()
+                                           .properties("name", "age", "city")
+                                           .primaryKeys("name", "age")
+                                           .nullableKeys("name")
+                                           .build();
+        Utils.assertResponseError(400, () -> {
+            vertexLabelAPI.create(vertexLabel2);
+        });
+
+        VertexLabel vertexLabel3 = schema().vertexLabel("person")
+                                           .usePrimaryKeyId()
+                                           .properties("name", "age", "city")
+                                           .primaryKeys("name")
+                                           .nullableKeys("name", "age")
+                                           .build();
+        Utils.assertResponseError(400, () -> {
+            vertexLabelAPI.create(vertexLabel3);
+        });
+    }
+
+    @Test
+    public void testCreateWithUnmatchIdStrategyAndProperties() {
+        Utils.assertResponseError(400, () -> {
+            VertexLabel vertexLabel = schema().vertexLabel("person")
+                                              .properties("name", "age", "city")
+                                              .useAutomaticId()
+                                              .primaryKeys("name")
+                                              .build();
+            vertexLabelAPI.create(vertexLabel);
+        });
+        Utils.assertResponseError(400, () -> {
+            VertexLabel vertexLabel = schema().vertexLabel("person")
+                                              .properties("name", "age", "city")
+                                              .useCustomizeStringId()
+                                              .primaryKeys("name")
+                                              .build();
+            vertexLabelAPI.create(vertexLabel);
+        });
+        Utils.assertResponseError(400, () -> {
+            VertexLabel vertexLabel = schema().vertexLabel("person")
+                                              .properties("name", "age", "city")
+                                              .usePrimaryKeyId()
+                                              .build();
+            vertexLabelAPI.create(vertexLabel);
+        });
+    }
+
+    @Test
+    public void testCreateWithTtl() {
+        VertexLabel vertexLabel = schema().vertexLabel("person1")
+                                          .useAutomaticId()
+                                          .properties("name", "age", "date")
+                                          .build();
+        vertexLabel = vertexLabelAPI.create(vertexLabel);
+
+        Assert.assertEquals("person1", vertexLabel.name());
+        Assert.assertEquals(IdStrategy.AUTOMATIC, vertexLabel.idStrategy());
+        Assert.assertEquals(true, vertexLabel.enableLabelIndex());
+        Set<String> props = ImmutableSet.of("name", "age", "date");
+        Assert.assertEquals(props, vertexLabel.properties());
+        Assert.assertEquals(0L, vertexLabel.ttl());
+        Assert.assertNull(vertexLabel.ttlStartTime());
+
+        vertexLabel = schema().vertexLabel("person2")
+                              .useAutomaticId()
+                              .properties("name", "age", "date")
+                              .ttl(3000L)
+                              .build();
+        vertexLabel = vertexLabelAPI.create(vertexLabel);
+
+        Assert.assertEquals("person2", vertexLabel.name());
+        Assert.assertEquals(IdStrategy.AUTOMATIC, vertexLabel.idStrategy());
+        Assert.assertEquals(true, vertexLabel.enableLabelIndex());
+        Assert.assertEquals(props, vertexLabel.properties());
+        Assert.assertEquals(3000L, vertexLabel.ttl());
+        Assert.assertNull(vertexLabel.ttlStartTime());
+
+        vertexLabel = schema().vertexLabel("person3")
+                              .useAutomaticId()
+                              .properties("name", "age", "date")
+                              .ttl(3000L)
+                              .ttlStartTime("date")
+                              .build();
+        vertexLabel = vertexLabelAPI.create(vertexLabel);
+
+        Assert.assertEquals("person3", vertexLabel.name());
+        Assert.assertEquals(IdStrategy.AUTOMATIC, vertexLabel.idStrategy());
+        Assert.assertEquals(true, vertexLabel.enableLabelIndex());
+        Assert.assertEquals(props, vertexLabel.properties());
+        Assert.assertEquals(3000L, vertexLabel.ttl());
+        Assert.assertEquals("date", vertexLabel.ttlStartTime());
+    }
+
+    @Test
+    public void testAppend() {
+        VertexLabel vertexLabel1 = schema().vertexLabel("person")
+                                           .useAutomaticId()
+                                           .properties("name", "age")
+                                           .build();
+        vertexLabel1 = vertexLabelAPI.create(vertexLabel1);
+        Assert.assertEquals("person", vertexLabel1.name());
+        Assert.assertEquals(IdStrategy.AUTOMATIC, vertexLabel1.idStrategy());
+        Set<String> props = ImmutableSet.of("name", "age");
+        Set<String> nullableKeys = ImmutableSet.of();
+        Assert.assertEquals(props, vertexLabel1.properties());
+        Assert.assertEquals(nullableKeys, vertexLabel1.nullableKeys());
+
+        VertexLabel vertexLabel2 = schema().vertexLabel("person")
+                                           .properties("city")
+                                           .nullableKeys("city")
+                                           .build();
+        vertexLabel2 = vertexLabelAPI.append(vertexLabel2);
+        Assert.assertEquals("person", vertexLabel2.name());
+        Assert.assertEquals(IdStrategy.AUTOMATIC, vertexLabel2.idStrategy());
+        props = ImmutableSet.of("name", "age", "city");
+        nullableKeys = ImmutableSet.of("city");
+        Assert.assertEquals(props, vertexLabel2.properties());
+        Assert.assertEquals(nullableKeys, vertexLabel2.nullableKeys());
+    }
+
+    @Test
+    public void testAppendWithUndefinedPropertyKey() {
+        VertexLabel vertexLabel1 = schema().vertexLabel("person")
+                                           .useAutomaticId()
+                                           .properties("name", "age")
+                                           .build();
+        vertexLabel1 = vertexLabelAPI.create(vertexLabel1);
+        Assert.assertEquals("person", vertexLabel1.name());
+        Assert.assertEquals(IdStrategy.AUTOMATIC, vertexLabel1.idStrategy());
+        Set<String> props = ImmutableSet.of("name", "age");
+        Assert.assertEquals(props, vertexLabel1.properties());
+
+        VertexLabel vertexLabel2 = schema().vertexLabel("person")
+                                           .properties("undefined")
+                                           .build();
+        Utils.assertResponseError(400, () -> {
+            vertexLabelAPI.append(vertexLabel2);
+        });
+    }
+
+    @Test
+    public void testAppendWithUndefinedNullabelKeys() {
+        VertexLabel vertexLabel1 = schema().vertexLabel("person")
+                                           .useAutomaticId()
+                                           .properties("name", "age")
+                                           .build();
+        vertexLabel1 = vertexLabelAPI.create(vertexLabel1);
+        Assert.assertEquals("person", vertexLabel1.name());
+        Assert.assertEquals(IdStrategy.AUTOMATIC, vertexLabel1.idStrategy());
+        Set<String> props = ImmutableSet.of("name", "age");
+        Assert.assertEquals(props, vertexLabel1.properties());
+
+        VertexLabel vertexLabel2 = schema().vertexLabel("person")
+                                           .nullableKeys("undefined")
+                                           .build();
+        Utils.assertResponseError(400, () -> {
+            vertexLabelAPI.append(vertexLabel2);
+        });
+    }
+
+    @Test
+    public void testEliminate() {
+        VertexLabel vertexLabel1 = schema().vertexLabel("person")
+                                           .useAutomaticId()
+                                           .properties("name", "age", "city")
+                                           .build();
+        vertexLabel1 = vertexLabelAPI.create(vertexLabel1);
+        Assert.assertEquals("person", vertexLabel1.name());
+        Assert.assertEquals(IdStrategy.AUTOMATIC, vertexLabel1.idStrategy());
+        Set<String> props = ImmutableSet.of("name", "age", "city");
+        Assert.assertEquals(props, vertexLabel1.properties());
+
+        VertexLabel vertexLabel2 = schema().vertexLabel("person")
+                                           .properties("city")
+                                           .build();
+        Utils.assertResponseError(400, () -> {
+            vertexLabelAPI.eliminate(vertexLabel2);
+        });
+    }
+
+    @Test
+    public void testGet() {
+        VertexLabel vertexLabel1 = schema().vertexLabel("person")
+                                           .useAutomaticId()
+                                           .properties("name", "age", "city")
+                                           .build();
+
+        vertexLabel1 = vertexLabelAPI.create(vertexLabel1);
+
+        VertexLabel vertexLabel2 = vertexLabelAPI.get("person");
+
+        Assert.assertEquals(vertexLabel1.name(), vertexLabel2.name());
+        Assert.assertEquals(vertexLabel1.idStrategy(),
+                            vertexLabel2.idStrategy());
+        Assert.assertEquals(vertexLabel1.properties(),
+                            vertexLabel2.properties());
+    }
+
+    @Test
+    public void testGetNotExist() {
+        Utils.assertResponseError(404, () -> {
+            vertexLabelAPI.get("not-exist-vl");
+        });
+    }
+
+    @Test
+    public void testList() {
+        VertexLabel vertexLabel1 = schema().vertexLabel("person")
+                                           .useAutomaticId()
+                                           .properties("name", "age", "city")
+                                           .build();
+        vertexLabel1 = vertexLabelAPI.create(vertexLabel1);
+
+        VertexLabel vertexLabel2 = schema().vertexLabel("software")
+                                           .useCustomizeStringId()
+                                           .properties("name", "lang", "price")
+                                           .build();
+        vertexLabel2 = vertexLabelAPI.create(vertexLabel2);
+
+        List<VertexLabel> vertexLabels = vertexLabelAPI.list();
+        Assert.assertEquals(2, vertexLabels.size());
+        assertContains(vertexLabels, vertexLabel1);
+        assertContains(vertexLabels, vertexLabel2);
+    }
+
+    @Test
+    public void testListByNames() {
+        VertexLabel person = schema().vertexLabel("person")
+                                     .useAutomaticId()
+                                     .properties("name", "age", "city")
+                                     .build();
+        person = vertexLabelAPI.create(person);
+
+        VertexLabel software = schema().vertexLabel("software")
+                                       .useCustomizeStringId()
+                                       .properties("name", "lang", "price")
+                                       .build();
+        software = vertexLabelAPI.create(software);
+
+        List<VertexLabel> vertexLabels;
+
+        vertexLabels = vertexLabelAPI.list(ImmutableList.of("person"));
+        Assert.assertEquals(1, vertexLabels.size());
+        assertContains(vertexLabels, person);
+
+        vertexLabels = vertexLabelAPI.list(ImmutableList.of("software"));
+        Assert.assertEquals(1, vertexLabels.size());
+        assertContains(vertexLabels, software);
+
+        vertexLabels = vertexLabelAPI.list(ImmutableList.of("person",
+                                                            "software"));
+        Assert.assertEquals(2, vertexLabels.size());
+        assertContains(vertexLabels, person);
+        assertContains(vertexLabels, software);
+    }
+
+    @Test
+    public void testDelete() {
+        VertexLabel vertexLabel = schema().vertexLabel("person")
+                                          .useAutomaticId()
+                                          .properties("name", "age", "city")
+                                          .build();
+        vertexLabelAPI.create(vertexLabel);
+
+        long taskId = vertexLabelAPI.delete("person");
+        waitUntilTaskCompleted(taskId);
+
+        Utils.assertResponseError(404, () -> {
+            vertexLabelAPI.get("person");
+        });
+    }
+
+    @Test
+    public void testDeleteNotExist() {
+        Utils.assertResponseError(404, () -> {
+            vertexLabelAPI.delete("not-exist-vl");
+        });
+    }
+
+    @Test
+    public void testAddVertexLabelWithUserData() {
+        VertexLabel player = schema().vertexLabel("player")
+                                     .properties("name")
+                                     .userdata("super_vl", "person")
+                                     .build();
+        player = vertexLabelAPI.create(player);
+        Assert.assertEquals(2, player.userdata().size());
+        Assert.assertEquals("person", player.userdata().get("super_vl"));
+        String time = (String) player.userdata().get("~create_time");
+        Date createTime = DateUtil.parse(time);
+        Assert.assertTrue(createTime.before(DateUtil.now()));
+
+        VertexLabel runner = schema().vertexLabel("runner")
+                                     .properties("name")
+                                     .userdata("super_vl", "person")
+                                     .userdata("super_vl", "player")
+                                     .build();
+        runner = vertexLabelAPI.create(runner);
+        // The same key user data will be overwritten
+        Assert.assertEquals(2, runner.userdata().size());
+        Assert.assertEquals("player", runner.userdata().get("super_vl"));
+        time = (String) runner.userdata().get("~create_time");
+        createTime = DateUtil.parse(time);
+        Assert.assertTrue(createTime.before(DateUtil.now()));
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/api/auth/AccessApiTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/auth/AccessApiTest.java
new file mode 100644
index 0000000..7b78d14
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/auth/AccessApiTest.java
@@ -0,0 +1,361 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.auth;
+
+import java.util.List;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.baidu.hugegraph.exception.ServerException;
+import com.baidu.hugegraph.structure.auth.Access;
+import com.baidu.hugegraph.structure.auth.Group;
+import com.baidu.hugegraph.structure.auth.HugePermission;
+import com.baidu.hugegraph.structure.auth.HugeResourceType;
+import com.baidu.hugegraph.structure.auth.Target;
+import com.baidu.hugegraph.testutil.Assert;
+import com.baidu.hugegraph.testutil.Whitebox;
+
+public class AccessApiTest extends AuthApiTest {
+
+    private static AccessAPI api;
+
+    private static Target gremlin;
+    private static Group group;
+
+    @BeforeClass
+    public static void init() {
+        api = new AccessAPI(initClient(), GRAPH);
+
+        TargetApiTest.init();
+        GroupApiTest.init();
+    }
+
+    @AfterClass
+    public static void clear() {
+        List<Access> accesss = api.list(null, null, -1);
+        for (Access access : accesss) {
+            api.delete(access.id());
+        }
+
+        TargetApiTest.clear();
+        GroupApiTest.clear();
+    }
+
+    @Before
+    @Override
+    public void setup() {
+        gremlin = TargetApiTest.createTarget("gremlin", HugeResourceType.GREMLIN);
+        group = GroupApiTest.createGroup("group-beijing", "group for beijing");
+    }
+
+    @After
+    @Override
+    public void teardown() {
+        clear();
+    }
+
+    @Test
+    public void testCreate() {
+        Access access1 = new Access();
+        access1.group(group);
+        access1.target(gremlin);
+        access1.permission(HugePermission.EXECUTE);
+        access1.description("group beijing execute gremlin");
+
+        Access access2 = new Access();
+        access2.group(group);
+        access2.target(gremlin);
+        access2.permission(HugePermission.READ);
+        access2.description("group beijing read gremlin");
+
+        Access result1 = api.create(access1);
+        Access result2 = api.create(access2);
+
+        Assert.assertEquals(group.id(), result1.group());
+        Assert.assertEquals(gremlin.id(), result1.target());
+        Assert.assertEquals(HugePermission.EXECUTE, result1.permission());
+        Assert.assertEquals("group beijing execute gremlin",
+                            result1.description());
+
+        Assert.assertEquals(group.id(), result2.group());
+        Assert.assertEquals(gremlin.id(), result2.target());
+        Assert.assertEquals(HugePermission.READ, result2.permission());
+        Assert.assertEquals("group beijing read gremlin",
+                            result2.description());
+
+        Assert.assertThrows(ServerException.class, () -> {
+            api.create(access1);
+        }, e -> {
+            Assert.assertContains("Can't save access", e.getMessage());
+            Assert.assertContains("that already exists", e.getMessage());
+        });
+
+        Assert.assertThrows(ServerException.class, () -> {
+            Access access3 = new Access();
+            access3.group(group);
+            access3.target(gremlin);
+            access3.permission(HugePermission.READ);
+            access3.description("group beijing read gremlin");
+            api.create(access3);
+        }, e -> {
+            Assert.assertContains("Can't save access", e.getMessage());
+            Assert.assertContains("that already exists", e.getMessage());
+        });
+    }
+
+    @Test
+    public void testGet() {
+        Access access1 = createAccess(HugePermission.WRITE, "description 1");
+        Access access2 = createAccess(HugePermission.READ, "description 2");
+
+        Assert.assertEquals("description 1", access1.description());
+        Assert.assertEquals("description 2", access2.description());
+
+        access1 = api.get(access1.id());
+        access2 = api.get(access2.id());
+
+        Assert.assertEquals(group.id(), access1.group());
+        Assert.assertEquals(gremlin.id(), access1.target());
+        Assert.assertEquals(HugePermission.WRITE, access1.permission());
+        Assert.assertEquals("description 1", access1.description());
+
+        Assert.assertEquals(group.id(), access2.group());
+        Assert.assertEquals(gremlin.id(), access2.target());
+        Assert.assertEquals(HugePermission.READ, access2.permission());
+        Assert.assertEquals("description 2", access2.description());
+    }
+
+    @Test
+    public void testList() {
+        createAccess(HugePermission.READ, "description 1");
+        createAccess(HugePermission.WRITE, "description 2");
+        createAccess(HugePermission.EXECUTE, "description 3");
+
+        List<Access> accesss = api.list(null, null, -1);
+        Assert.assertEquals(3, accesss.size());
+
+        accesss.sort((t1, t2) -> t1.permission().compareTo(t2.permission()));
+        Assert.assertEquals("description 1", accesss.get(0).description());
+        Assert.assertEquals("description 2", accesss.get(1).description());
+        Assert.assertEquals("description 3", accesss.get(2).description());
+
+        accesss = api.list(null, null, 1);
+        Assert.assertEquals(1, accesss.size());
+
+        accesss = api.list(null, null, 2);
+        Assert.assertEquals(2, accesss.size());
+
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            api.list(null, null, 0);
+        }, e -> {
+            Assert.assertContains("Limit must be > 0 or == -1", e.getMessage());
+        });
+    }
+
+    @Test
+    public void testListByGroup() {
+        createAccess(HugePermission.READ, "description 1");
+        createAccess(HugePermission.WRITE, "description 2");
+        createAccess(HugePermission.EXECUTE, "description 3");
+
+        Group hk = GroupApiTest.createGroup("group-hk", "group for hongkong");
+        createAccess(hk, gremlin, HugePermission.READ, "description 4");
+        createAccess(hk, gremlin, HugePermission.WRITE, "description 5");
+
+        List<Access> accesss = api.list(null, null, -1);
+        Assert.assertEquals(5, accesss.size());
+
+        accesss = api.list(hk, null, -1);
+        Assert.assertEquals(2, accesss.size());
+        accesss.sort((t1, t2) -> t1.permission().compareTo(t2.permission()));
+        Assert.assertEquals("description 4", accesss.get(0).description());
+        Assert.assertEquals("description 5", accesss.get(1).description());
+
+        accesss = api.list(group, null, -1);
+        Assert.assertEquals(3, accesss.size());
+        accesss.sort((t1, t2) -> t1.permission().compareTo(t2.permission()));
+        Assert.assertEquals("description 1", accesss.get(0).description());
+        Assert.assertEquals("description 2", accesss.get(1).description());
+        Assert.assertEquals("description 3", accesss.get(2).description());
+
+        accesss = api.list(group, null, 1);
+        Assert.assertEquals(1, accesss.size());
+
+        accesss = api.list(group, null, 2);
+        Assert.assertEquals(2, accesss.size());
+
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            api.list(group, null, 0);
+        }, e -> {
+            Assert.assertContains("Limit must be > 0 or == -1", e.getMessage());
+        });
+
+        Assert.assertThrows(ServerException.class, () -> {
+            api.list(group, gremlin, -1);
+        }, e -> {
+            Assert.assertContains("Can't pass both group and target " +
+                                  "at the same time", e.getMessage());
+        });
+    }
+
+    @Test
+    public void testListByTarget() {
+        createAccess(HugePermission.READ, "description 1");
+        createAccess(HugePermission.WRITE, "description 2");
+        createAccess(HugePermission.EXECUTE, "description 3");
+
+        Group hk = GroupApiTest.createGroup("group-hk", "group for hongkong");
+        createAccess(hk, gremlin, HugePermission.READ, "description 4");
+        createAccess(hk, gremlin, HugePermission.WRITE, "description 5");
+
+        Target task = TargetApiTest.createTarget("task", HugeResourceType.TASK);
+        createAccess(hk, task, HugePermission.READ, "description 6");
+        createAccess(hk, task, HugePermission.WRITE, "description 7");
+
+        List<Access> accesss = api.list(null, null, -1);
+        Assert.assertEquals(7, accesss.size());
+
+        accesss = api.list(null, task, -1);
+        Assert.assertEquals(2, accesss.size());
+        accesss.sort((t1, t2) -> t1.permission().compareTo(t2.permission()));
+        Assert.assertEquals("description 6", accesss.get(0).description());
+        Assert.assertEquals("description 7", accesss.get(1).description());
+
+        accesss = api.list(null, gremlin, -1);
+        Assert.assertEquals(5, accesss.size());
+        accesss.sort((t1, t2) -> {
+            String s1 = "" + t1.group() + t1.permission().ordinal();
+            String s2 = "" + t2.group() + t2.permission().ordinal();
+            return s1.compareTo(s2);
+        });
+        Assert.assertEquals("description 1", accesss.get(0).description());
+        Assert.assertEquals("description 2", accesss.get(1).description());
+        Assert.assertEquals("description 3", accesss.get(2).description());
+        Assert.assertEquals("description 4", accesss.get(3).description());
+        Assert.assertEquals("description 5", accesss.get(4).description());
+
+        accesss = api.list(null, gremlin, 1);
+        Assert.assertEquals(1, accesss.size());
+
+        accesss = api.list(null, gremlin, 2);
+        Assert.assertEquals(2, accesss.size());
+
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            api.list(null, gremlin, 0);
+        }, e -> {
+            Assert.assertContains("Limit must be > 0 or == -1", e.getMessage());
+        });
+
+        Assert.assertThrows(ServerException.class, () -> {
+            api.list(hk, task, -1);
+        }, e -> {
+            Assert.assertContains("Can't pass both group and target " +
+                                  "at the same time", e.getMessage());
+        });
+    }
+
+    @Test
+    public void testUpdate() {
+        Access access1 = createAccess(HugePermission.WRITE, "description 1");
+        Access access2 = createAccess(HugePermission.READ, "description 2");
+
+        Assert.assertEquals("description 1", access1.description());
+        Assert.assertEquals("description 2", access2.description());
+
+        access1.description("description updated");
+        Access updated = api.update(access1);
+        Assert.assertEquals("description updated", updated.description());
+        Assert.assertNotEquals(access1.updateTime(), updated.updateTime());
+
+        Assert.assertThrows(ServerException.class, () -> {
+            Group hk = GroupApiTest.createGroup("group-hk", "");
+            access2.group(hk);
+            api.update(access2);
+        }, e -> {
+            Assert.assertContains("The group of access can't be updated",
+                                  e.getMessage());
+        });
+
+        Assert.assertThrows(ServerException.class, () -> {
+            Target task = TargetApiTest.createTarget("task",
+                                                     HugeResourceType.TASK);
+            access2.group(group);
+            access2.target(task);
+            api.update(access2);
+        }, e -> {
+            Assert.assertContains("The target of access can't be updated",
+                                  e.getMessage());
+        });
+
+        Assert.assertThrows(ServerException.class, () -> {
+            Whitebox.setInternalState(access2, "id", "fake-id");
+            api.update(access2);
+        }, e -> {
+            Assert.assertContains("Invalid access id: fake-id",
+                                  e.getMessage());
+        });
+    }
+
+    @Test
+    public void testDelete() {
+        Access access1 = createAccess(HugePermission.WRITE, "description 1");
+        Access access2 = createAccess(HugePermission.READ, "description 2");
+
+        Assert.assertEquals(2, api.list(null, null, -1).size());
+        api.delete(access1.id());
+
+        Assert.assertEquals(1, api.list(null, null, -1).size());
+        Assert.assertEquals(access2, api.list(null, null, -1).get(0));
+
+        api.delete(access2.id());
+        Assert.assertEquals(0, api.list(null, null, -1).size());
+
+        Assert.assertThrows(ServerException.class, () -> {
+            api.delete(access2.id());
+        }, e -> {
+            Assert.assertContains("Invalid access id:", e.getMessage());
+        });
+
+        Assert.assertThrows(ServerException.class, () -> {
+            api.delete("fake-id");
+        }, e -> {
+            Assert.assertContains("Invalid access id: fake-id",
+                                  e.getMessage());
+        });
+    }
+
+    private Access createAccess(HugePermission perm, String description) {
+        return createAccess(group, gremlin, perm, description);
+    }
+
+    private Access createAccess(Group group, Target target,
+                                HugePermission perm, String description) {
+        Access access = new Access();
+        access.group(group);
+        access.target(target);
+        access.permission(perm);
+        access.description(description);
+        return api.create(access);
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/api/auth/AuthApiTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/auth/AuthApiTest.java
new file mode 100644
index 0000000..346c33e
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/auth/AuthApiTest.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.auth;
+
+import com.baidu.hugegraph.api.BaseApiTest;
+
+public class AuthApiTest extends BaseApiTest {
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/api/auth/BelongApiTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/auth/BelongApiTest.java
new file mode 100644
index 0000000..d90bb50
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/auth/BelongApiTest.java
@@ -0,0 +1,326 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.auth;
+
+import java.util.List;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.baidu.hugegraph.exception.ServerException;
+import com.baidu.hugegraph.structure.auth.Belong;
+import com.baidu.hugegraph.structure.auth.Group;
+import com.baidu.hugegraph.structure.auth.User;
+import com.baidu.hugegraph.testutil.Assert;
+import com.baidu.hugegraph.testutil.Whitebox;
+
+public class BelongApiTest extends AuthApiTest {
+
+    private static BelongAPI api;
+
+    private static User user1;
+    private static User user2;
+    private static Group group1;
+    private static Group group2;
+
+    @BeforeClass
+    public static void init() {
+        api = new BelongAPI(initClient(), GRAPH);
+
+        UserApiTest.init();
+        GroupApiTest.init();
+    }
+
+    @AfterClass
+    public static void clear() {
+        List<Belong> belongs = api.list(null, null, -1);
+        for (Belong belong : belongs) {
+            api.delete(belong.id());
+        }
+
+        UserApiTest.clear();
+        GroupApiTest.clear();
+    }
+
+    @Before
+    @Override
+    public void setup() {
+        user1 = UserApiTest.createUser("user-1", "p1");
+        user2 = UserApiTest.createUser("user-2", "p2");
+        group1 = GroupApiTest.createGroup("group-1", "group 1");
+        group2 = GroupApiTest.createGroup("group-2", "group 2");
+    }
+
+    @After
+    @Override
+    public void teardown() {
+        clear();
+    }
+
+    @Test
+    public void testCreate() {
+        Belong belong1 = new Belong();
+        belong1.user(user1);
+        belong1.group(group1);
+        belong1.description("user 1 => group 1");
+
+        Belong belong2 = new Belong();
+        belong2.user(user1);
+        belong2.group(group2);
+        belong2.description("user 1 => group 2");
+
+        Belong belong3 = new Belong();
+        belong3.user(user2);
+        belong3.group(group2);
+        belong3.description("user 2 => group 2");
+
+        Belong result1 = api.create(belong1);
+        Belong result2 = api.create(belong2);
+        Belong result3 = api.create(belong3);
+
+        Assert.assertEquals(user1.id(), result1.user());
+        Assert.assertEquals(group1.id(), result1.group());
+        Assert.assertEquals("user 1 => group 1", result1.description());
+
+        Assert.assertEquals(user1.id(), result2.user());
+        Assert.assertEquals(group2.id(), result2.group());
+        Assert.assertEquals("user 1 => group 2", result2.description());
+
+        Assert.assertEquals(user2.id(), result3.user());
+        Assert.assertEquals(group2.id(), result3.group());
+        Assert.assertEquals("user 2 => group 2", result3.description());
+
+        Assert.assertThrows(ServerException.class, () -> {
+            api.create(belong1);
+        }, e -> {
+            Assert.assertContains("Can't save belong", e.getMessage());
+            Assert.assertContains("that already exists", e.getMessage());
+        });
+
+        Assert.assertThrows(ServerException.class, () -> {
+            Belong belong4 = new Belong();
+            belong4.user(user1);
+            belong4.group(group1);
+            api.create(belong3);
+        }, e -> {
+            Assert.assertContains("Can't save belong", e.getMessage());
+            Assert.assertContains("that already exists", e.getMessage());
+        });
+    }
+
+    @Test
+    public void testGet() {
+        Belong belong1 = createBelong(user1, group1, "user1 => group1");
+        Belong belong2 = createBelong(user2, group2, "user2 => group2");
+
+        Assert.assertEquals("user1 => group1", belong1.description());
+        Assert.assertEquals("user2 => group2", belong2.description());
+
+        belong1 = api.get(belong1.id());
+        belong2 = api.get(belong2.id());
+
+        Assert.assertEquals(user1.id(), belong1.user());
+        Assert.assertEquals(group1.id(), belong1.group());
+        Assert.assertEquals("user1 => group1", belong1.description());
+
+        Assert.assertEquals(user2.id(), belong2.user());
+        Assert.assertEquals(group2.id(), belong2.group());
+        Assert.assertEquals("user2 => group2", belong2.description());
+    }
+
+    @Test
+    public void testList() {
+        createBelong(user1, group1, "user1 => group1");
+        createBelong(user1, group2, "user1 => group2");
+        createBelong(user2, group2, "user2 => group2");
+
+        List<Belong> belongs = api.list(null, null, -1);
+        Assert.assertEquals(3, belongs.size());
+
+        belongs.sort((t1, t2) -> t1.description().compareTo(t2.description()));
+        Assert.assertEquals("user1 => group1", belongs.get(0).description());
+        Assert.assertEquals("user1 => group2", belongs.get(1).description());
+        Assert.assertEquals("user2 => group2", belongs.get(2).description());
+
+        belongs = api.list(null, null, 1);
+        Assert.assertEquals(1, belongs.size());
+
+        belongs = api.list(null, null, 2);
+        Assert.assertEquals(2, belongs.size());
+
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            api.list(null, null, 0);
+        }, e -> {
+            Assert.assertContains("Limit must be > 0 or == -1", e.getMessage());
+        });
+    }
+
+    @Test
+    public void testListByUser() {
+        createBelong(user1, group1, "user1 => group1");
+        createBelong(user1, group2, "user1 => group2");
+        createBelong(user2, group2, "user2 => group2");
+
+        List<Belong> belongs = api.list(null, null, -1);
+        Assert.assertEquals(3, belongs.size());
+
+        belongs = api.list(user2, null, -1);
+        Assert.assertEquals(1, belongs.size());
+        Assert.assertEquals("user2 => group2", belongs.get(0).description());
+
+        belongs = api.list(user1, null, -1);
+        Assert.assertEquals(2, belongs.size());
+
+        belongs.sort((t1, t2) -> t1.description().compareTo(t2.description()));
+        Assert.assertEquals("user1 => group1", belongs.get(0).description());
+        Assert.assertEquals("user1 => group2", belongs.get(1).description());
+
+        belongs = api.list(user1, null, 1);
+        Assert.assertEquals(1, belongs.size());
+
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            api.list(user1, null, 0);
+        }, e -> {
+            Assert.assertContains("Limit must be > 0 or == -1", e.getMessage());
+        });
+
+        Assert.assertThrows(ServerException.class, () -> {
+            api.list(user1, group1, -1);
+        }, e -> {
+            Assert.assertContains("Can't pass both user and group " +
+                                  "at the same time", e.getMessage());
+        });
+    }
+
+    @Test
+    public void testListByGroup() {
+        createBelong(user1, group1, "user1 => group1");
+        createBelong(user1, group2, "user1 => group2");
+        createBelong(user2, group2, "user2 => group2");
+
+        List<Belong> belongs = api.list(null, null, -1);
+        Assert.assertEquals(3, belongs.size());
+
+        belongs = api.list(null, group1, -1);
+        Assert.assertEquals(1, belongs.size());
+        Assert.assertEquals("user1 => group1", belongs.get(0).description());
+
+        belongs = api.list(null, group2, -1);
+        Assert.assertEquals(2, belongs.size());
+
+        belongs.sort((t1, t2) -> t1.description().compareTo(t2.description()));
+        Assert.assertEquals("user1 => group2", belongs.get(0).description());
+        Assert.assertEquals("user2 => group2", belongs.get(1).description());
+
+        belongs = api.list(null, group2, 1);
+        Assert.assertEquals(1, belongs.size());
+
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            api.list(null, group2, 0);
+        }, e -> {
+            Assert.assertContains("Limit must be > 0 or == -1", e.getMessage());
+        });
+
+        Assert.assertThrows(ServerException.class, () -> {
+            api.list(user2, group2, -1);
+        }, e -> {
+            Assert.assertContains("Can't pass both user and group " +
+                                  "at the same time", e.getMessage());
+        });
+    }
+
+    @Test
+    public void testUpdate() {
+        Belong belong1 = createBelong(user1, group1, "user1 => group1");
+        Belong belong2 = createBelong(user2, group2, "user2 => group2");
+
+        Assert.assertEquals("user1 => group1", belong1.description());
+        Assert.assertEquals("user2 => group2", belong2.description());
+
+        belong1.description("description updated");
+        Belong updated = api.update(belong1);
+        Assert.assertEquals("description updated", updated.description());
+        Assert.assertNotEquals(belong1.updateTime(), updated.updateTime());
+
+        Assert.assertThrows(ServerException.class, () -> {
+            belong2.user(user1);
+            api.update(belong2);
+        }, e -> {
+            Assert.assertContains("The user of belong can't be updated",
+                                  e.getMessage());
+        });
+
+        Assert.assertThrows(ServerException.class, () -> {
+            belong2.user(user2);
+            belong2.group(group1);
+            api.update(belong2);
+        }, e -> {
+            Assert.assertContains("The group of belong can't be updated",
+                                  e.getMessage());
+        });
+
+        Assert.assertThrows(ServerException.class, () -> {
+            Whitebox.setInternalState(belong2, "id", "fake-id");
+            api.update(belong2);
+        }, e -> {
+            Assert.assertContains("Invalid belong id: fake-id",
+                                  e.getMessage());
+        });
+    }
+
+    @Test
+    public void testDelete() {
+        Belong belong1 = createBelong(user1, group1, "user1 => group1");
+        Belong belong2 = createBelong(user2, group2, "user2 => group2");
+
+        Assert.assertEquals(2, api.list(null, null, -1).size());
+        api.delete(belong1.id());
+
+        Assert.assertEquals(1, api.list(null, null, -1).size());
+        Assert.assertEquals(belong2, api.list(null, null, -1).get(0));
+
+        api.delete(belong2.id());
+        Assert.assertEquals(0, api.list(null, null, -1).size());
+
+        Assert.assertThrows(ServerException.class, () -> {
+            api.delete(belong2.id());
+        }, e -> {
+            Assert.assertContains("Invalid belong id:", e.getMessage());
+        });
+
+        Assert.assertThrows(ServerException.class, () -> {
+            api.delete("fake-id");
+        }, e -> {
+            Assert.assertContains("Invalid belong id: fake-id",
+                                  e.getMessage());
+        });
+    }
+
+    private Belong createBelong(User user, Group group, String description) {
+        Belong belong = new Belong();
+        belong.user(user);
+        belong.group(group);
+        belong.description(description);
+        return api.create(belong);
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/api/auth/GroupApiTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/auth/GroupApiTest.java
new file mode 100644
index 0000000..f0fe4ca
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/auth/GroupApiTest.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.auth;
+
+import java.util.List;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.baidu.hugegraph.exception.ServerException;
+import com.baidu.hugegraph.structure.auth.Group;
+import com.baidu.hugegraph.testutil.Assert;
+import com.baidu.hugegraph.testutil.Whitebox;
+
+public class GroupApiTest extends AuthApiTest {
+
+    private static GroupAPI api;
+
+    @BeforeClass
+    public static void init() {
+        api = new GroupAPI(initClient(), GRAPH);
+    }
+
+    @AfterClass
+    public static void clear() {
+        List<Group> groups = api.list(-1);
+        for (Group group : groups) {
+            api.delete(group.id());
+        }
+    }
+
+    @Override
+    @After
+    public void teardown() {
+        clear();
+    }
+
+    @Test
+    public void testCreate() {
+        Group group1 = new Group();
+        group1.name("group-beijing");
+        group1.description("group for users in beijing");
+
+        Group group2 = new Group();
+        group2.name("group-shanghai");
+        group2.description("group for users in shanghai");
+
+        Group result1 = api.create(group1);
+        Group result2 = api.create(group2);
+
+        Assert.assertEquals("group-beijing", result1.name());
+        Assert.assertEquals("group for users in beijing",
+                            result1.description());
+        Assert.assertEquals("group-shanghai", result2.name());
+        Assert.assertEquals("group for users in shanghai",
+                            result2.description());
+
+        Assert.assertThrows(ServerException.class, () -> {
+            api.create(group1);
+        }, e -> {
+            Assert.assertContains("Can't save group", e.getMessage());
+            Assert.assertContains("that already exists", e.getMessage());
+        });
+
+        Assert.assertThrows(ServerException.class, () -> {
+            api.create(new Group());
+        }, e -> {
+            Assert.assertContains("The name of group can't be null",
+                                  e.getMessage());
+        });
+    }
+
+    @Test
+    public void testGet() {
+        Group group1 = createGroup("test1", "description 1");
+        Group group2 = createGroup("test2", "description 2");
+
+        Assert.assertEquals("description 1", group1.description());
+        Assert.assertEquals("description 2", group2.description());
+
+        group1 = api.get(group1.id());
+        group2 = api.get(group2.id());
+
+        Assert.assertEquals("test1", group1.name());
+        Assert.assertEquals("description 1", group1.description());
+
+        Assert.assertEquals("test2", group2.name());
+        Assert.assertEquals("description 2", group2.description());
+    }
+
+    @Test
+    public void testList() {
+        createGroup("test1", "description 1");
+        createGroup("test2", "description 2");
+        createGroup("test3", "description 3");
+
+        List<Group> groups = api.list(-1);
+        Assert.assertEquals(3, groups.size());
+
+        groups.sort((t1, t2) -> t1.name().compareTo(t2.name()));
+        Assert.assertEquals("test1", groups.get(0).name());
+        Assert.assertEquals("test2", groups.get(1).name());
+        Assert.assertEquals("test3", groups.get(2).name());
+        Assert.assertEquals("description 1", groups.get(0).description());
+        Assert.assertEquals("description 2", groups.get(1).description());
+        Assert.assertEquals("description 3", groups.get(2).description());
+
+        groups = api.list(1);
+        Assert.assertEquals(1, groups.size());
+
+        groups = api.list(2);
+        Assert.assertEquals(2, groups.size());
+
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            api.list(0);
+        }, e -> {
+            Assert.assertContains("Limit must be > 0 or == -1", e.getMessage());
+        });
+    }
+
+    @Test
+    public void testUpdate() {
+        Group group1 = createGroup("test1", "description 1");
+        Group group2 = createGroup("test2", "description 2");
+
+        Assert.assertEquals("description 1", group1.description());
+        Assert.assertEquals("description 2", group2.description());
+
+        group1.description("description updated");
+        Group updated = api.update(group1);
+        Assert.assertEquals("description updated", updated.description());
+        Assert.assertNotEquals(group1.updateTime(), updated.updateTime());
+
+        Assert.assertThrows(ServerException.class, () -> {
+            group2.name("test2-updated");
+            api.update(group2);
+        }, e -> {
+            Assert.assertContains("The name of group can't be updated",
+                                  e.getMessage());
+        });
+
+        Assert.assertThrows(ServerException.class, () -> {
+            Whitebox.setInternalState(group2, "id", "fake-id");
+            api.update(group2);
+        }, e -> {
+            Assert.assertContains("Invalid group id: fake-id",
+                                  e.getMessage());
+        });
+    }
+
+    @Test
+    public void testDelete() {
+        Group group1 = createGroup("test1", "description 1");
+        Group group2 = createGroup("test2", "description 2");
+
+        Assert.assertEquals(2, api.list(-1).size());
+        api.delete(group1.id());
+
+        Assert.assertEquals(1, api.list(-1).size());
+        Assert.assertEquals(group2, api.list(-1).get(0));
+
+        api.delete(group2.id());
+        Assert.assertEquals(0, api.list(-1).size());
+
+        Assert.assertThrows(ServerException.class, () -> {
+            api.delete(group2.id());
+        }, e -> {
+            Assert.assertContains("Invalid group id:", e.getMessage());
+        });
+
+        Assert.assertThrows(ServerException.class, () -> {
+            api.delete("fake-id");
+        }, e -> {
+            Assert.assertContains("Invalid group id: fake-id",
+                                  e.getMessage());
+        });
+    }
+
+    protected static Group createGroup(String name, String description) {
+        Group group = new Group();
+        group.name(name);
+        group.description(description);
+        return api.create(group);
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/api/auth/LoginApiTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/auth/LoginApiTest.java
new file mode 100644
index 0000000..6b02c40
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/auth/LoginApiTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.auth;
+
+import java.util.List;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.baidu.hugegraph.exception.ServerException;
+import com.baidu.hugegraph.structure.auth.Login;
+import com.baidu.hugegraph.structure.auth.LoginResult;
+import com.baidu.hugegraph.structure.auth.User;
+import com.baidu.hugegraph.testutil.Assert;
+
+public class LoginApiTest extends AuthApiTest {
+
+    private static LoginAPI loginAPI;
+    private static UserAPI userAPI;
+
+    @BeforeClass
+    public static void init() {
+        loginAPI = new LoginAPI(initClient(), GRAPH);
+        userAPI = new UserAPI(initClient(), GRAPH);
+    }
+
+    @AfterClass
+    public static void clear() {
+        List<User> users = userAPI.list(-1);
+        for (User user : users) {
+            if (user.name().equals("admin")) {
+                continue;
+            }
+            userAPI.delete(user.id());
+        }
+    }
+
+    @Test
+    public void testLogin() {
+        Login login = new Login();
+        login.name("user1");
+        login.password("p1");
+        Assert.assertThrows(ServerException.class, () -> {
+            loginAPI.login(login);
+        }, e -> {
+            Assert.assertContains("Incorrect username or password",
+                                  e.getMessage());
+        });
+
+        User user1 = new User();
+        user1.name("user1");
+        user1.password("p1");
+        userAPI.create(user1);
+
+        LoginResult result = loginAPI.login(login);
+        Assert.assertNotNull(result);
+        Assert.assertNotNull(result.token());
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/api/auth/LogoutApiTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/auth/LogoutApiTest.java
new file mode 100644
index 0000000..4a91a21
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/auth/LogoutApiTest.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.auth;
+
+import java.util.List;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.exception.ServerException;
+import com.baidu.hugegraph.structure.auth.Login;
+import com.baidu.hugegraph.structure.auth.LoginResult;
+import com.baidu.hugegraph.structure.auth.User;
+import com.baidu.hugegraph.testutil.Assert;
+import com.baidu.hugegraph.testutil.Whitebox;
+
+public class LogoutApiTest extends AuthApiTest {
+
+    private static LogoutAPI logoutAPI;
+    private static LoginAPI loginAPI;
+    private static UserAPI userAPI;
+
+    @BeforeClass
+    public static void init() {
+        logoutAPI = new LogoutAPI(initClient(), GRAPH);
+        loginAPI = new LoginAPI(initClient(), GRAPH);
+        userAPI = new UserAPI(initClient(), GRAPH);
+    }
+
+    @AfterClass
+    public static void clear() {
+        List<User> users = userAPI.list(-1);
+        for (User user : users) {
+            if (user.name().equals("admin")) {
+                continue;
+            }
+            userAPI.delete(user.id());
+        }
+    }
+
+    @Test
+    public void testLogout() {
+        User user1 = new User();
+        user1.name("user1");
+        user1.password("p1");
+        userAPI.create(user1);
+
+        Login login = new Login();
+        login.name("user1");
+        login.password("p1");
+        LoginResult result = loginAPI.login(login);
+        Assert.assertNotNull(result);
+        Assert.assertNotNull(result.token());
+
+        // Client will set Authentication Header use Basic
+        Assert.assertThrows(ServerException.class, () -> {
+            logoutAPI.logout();
+        }, e -> {
+            Assert.assertContains("Only HTTP Bearer authentication is supported",
+                                  e.getMessage());
+        });
+
+        String token = result.token();
+        RestClient client = Whitebox.getInternalState(logoutAPI, "client");
+        client.setAuthContext("Bearer " + token);
+        logoutAPI.logout();
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/api/auth/ProjectApiTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/auth/ProjectApiTest.java
new file mode 100644
index 0000000..bc4c5fb
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/auth/ProjectApiTest.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.auth;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang.StringUtils;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.baidu.hugegraph.api.API;
+import com.baidu.hugegraph.exception.ServerException;
+import com.baidu.hugegraph.structure.auth.Project;
+import com.baidu.hugegraph.testutil.Assert;
+import com.google.common.collect.ImmutableSet;
+
+public class ProjectApiTest extends AuthApiTest {
+
+    private static ProjectAPI api;
+
+    @BeforeClass
+    public static void init() {
+        api = new ProjectAPI(initClient(), GRAPH);
+    }
+
+    @AfterClass
+    public static void clear() {
+        List<Project> projects = api.list(-1);
+        for (Project project : projects) {
+            Set<String> graphs = project.graphs();
+            if (CollectionUtils.isNotEmpty(graphs)) {
+                api.removeGraphs(project, graphs);
+            }
+            api.delete(project.id());
+        }
+    }
+
+    @Override
+    @After
+    public void teardown() throws Exception {
+        super.teardown();
+        clear();
+    }
+
+    @Test
+    public void testGet() {
+        Project createdProject = createProject("project_test");
+        Project fetchedProject = api.get(createdProject);
+        Assert.assertEquals(createdProject, fetchedProject);
+    }
+
+    @Test
+    public void testCreate() {
+        Project paramsProject = new Project("project_test",
+                                            "project_description");
+        Project createdProject = api.create(paramsProject);
+        Assert.assertEquals(paramsProject.name(), createdProject.name());
+        Assert.assertEquals(paramsProject.description(),
+                            createdProject.description());
+    }
+
+    @Test
+    public void testList() {
+        Project project1 = createProject("project_test1");
+        Project project2 = createProject("project_test2");
+        Project project3 = createProject("project_test3");
+        List<Project> allProject = api.list(API.NO_LIMIT);
+        Assert.assertTrue(allProject.contains(project1));
+        Assert.assertTrue(allProject.contains(project2));
+        Assert.assertTrue(allProject.contains(project3));
+        List<Project> projects = api.list(1);
+        Assert.assertEquals(1, projects.size());
+        Project project = projects.get(0);
+        Assert.assertTrue(StringUtils.isNotEmpty(project.adminGroup()));
+        Assert.assertTrue(StringUtils.isNotEmpty(project.opGroup()));
+        Assert.assertTrue(StringUtils.isNotEmpty(project.target()));
+        Assert.assertTrue(StringUtils.isNotEmpty(project.creator()));
+        Assert.assertNotNull(project.createTime());
+        Assert.assertNotNull(project.updateTime());
+    }
+
+    @Test
+    public void testDelete() {
+        Project project = createProject("project_test");
+        Assert.assertNotNull(project);
+        Assert.assertEquals(project, api.get(project));
+        api.delete(project);
+        Assert.assertThrows(ServerException.class, () -> {
+            api.get(project);
+        }, e -> {
+            Assert.assertTrue(e.getMessage().contains("Invalid project id"));
+        });
+    }
+
+    @Test
+    public void testAddGraph() {
+        Project project = createProject("project_test");
+        api.addGraphs(project, ImmutableSet.of("test_graph"));
+        project = getProject(project);
+        Assert.assertEquals(1, project.graphs().size());
+        Assert.assertTrue(project.graphs().contains("test_graph"));
+        api.addGraphs(project, ImmutableSet.of("test_graph1"));
+        project = getProject(project);
+        Assert.assertEquals(2, project.graphs().size());
+        Assert.assertTrue(project.graphs().contains("test_graph1"));
+    }
+
+    @Test
+    public void testRemoveGraph() {
+        Set<String> graphs = ImmutableSet.of("test_graph1",
+                                             "test_graph2",
+                                             "test_graph3");
+        Project project = createProject("project_test", graphs);
+        graphs = new HashSet<>(graphs);
+        Assert.assertTrue(graphs.containsAll(project.graphs()));
+        project = api.removeGraphs(project, ImmutableSet.of("test_graph1"));
+        graphs.remove("test_graph1");
+        Assert.assertTrue(graphs.containsAll(project.graphs()));
+        project = api.removeGraphs(project, ImmutableSet.of("test_graph2"));
+        graphs.remove("test_graph2");
+        Assert.assertTrue(graphs.containsAll(project.graphs()));
+        project = api.removeGraphs(project, ImmutableSet.of("test_graph3"));
+        graphs.remove("test_graph3");
+        Assert.assertEquals(0, graphs.size());
+        Assert.assertNull(project.graphs());
+    }
+
+    private static Project createProject(String name) {
+        Project project = new Project(name);
+        return api.create(project);
+    }
+
+    private static Project createProject(String name, Set<String> graphs) {
+        Project project = new Project(name);
+        project = api.create(project);
+        project = api.addGraphs(project.id(), graphs);
+        return project;
+    }
+
+    private static Project getProject(Object id) {
+        return api.get(id);
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/api/auth/TargetApiTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/auth/TargetApiTest.java
new file mode 100644
index 0000000..0770ed4
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/auth/TargetApiTest.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.auth;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.baidu.hugegraph.exception.ServerException;
+import com.baidu.hugegraph.structure.auth.HugeResource;
+import com.baidu.hugegraph.structure.auth.HugeResourceType;
+import com.baidu.hugegraph.structure.auth.Target;
+import com.baidu.hugegraph.testutil.Assert;
+import com.baidu.hugegraph.testutil.Whitebox;
+
+public class TargetApiTest extends AuthApiTest {
+
+    private static TargetAPI api;
+
+    @BeforeClass
+    public static void init() {
+        api = new TargetAPI(initClient(), GRAPH);
+    }
+
+    @AfterClass
+    public static void clear() {
+        List<Target> targets = api.list(-1);
+        for (Target target : targets) {
+            api.delete(target.id());
+        }
+    }
+
+    @Override
+    @After
+    public void teardown() {
+        clear();
+    }
+
+    @Test
+    public void testCreate() {
+        Target target1 = new Target();
+        target1.name("gremlin");
+        target1.graph("hugegraph");
+        target1.url("127.0.0.1:8080");
+        HugeResource gremlin = new HugeResource(HugeResourceType.GREMLIN);
+        target1.resources(gremlin);
+
+        Target target2 = new Target();
+        target2.name("task");
+        target2.graph("hugegraph2");
+        target2.url("127.0.0.1:8081");
+        HugeResource task = new HugeResource(HugeResourceType.TASK);
+        target2.resources(task);
+
+        Target result1 = api.create(target1);
+        Target result2 = api.create(target2);
+
+        Assert.assertEquals("gremlin", result1.name());
+        Assert.assertEquals("hugegraph", result1.graph());
+        Assert.assertEquals("127.0.0.1:8080", result1.url());
+        Assert.assertEquals(Arrays.asList(gremlin), result1.resources());
+
+        Assert.assertEquals("task", result2.name());
+        Assert.assertEquals("hugegraph2", result2.graph());
+        Assert.assertEquals("127.0.0.1:8081", result2.url());
+        Assert.assertEquals(Arrays.asList(task), result2.resources());
+
+        Assert.assertThrows(ServerException.class, () -> {
+            api.create(target1);
+        }, e -> {
+            Assert.assertContains("Can't save target", e.getMessage());
+            Assert.assertContains("that already exists", e.getMessage());
+        });
+
+        Assert.assertThrows(ServerException.class, () -> {
+            Target target3 = new Target();
+            api.create(target3);
+        }, e -> {
+            Assert.assertContains("The name of target can't be null",
+                                  e.getMessage());
+        });
+
+        Assert.assertThrows(ServerException.class, () -> {
+            Target target3 = new Target();
+            target3.name("test");
+            api.create(target3);
+        }, e -> {
+            Assert.assertContains("The graph of target can't be null",
+                                  e.getMessage());
+        });
+
+        Assert.assertThrows(ServerException.class, () -> {
+            Target target3 = new Target();
+            target3.name("test");
+            target3.graph("hugegraph3");
+            api.create(target3);
+        }, e -> {
+            Assert.assertContains("The url of target can't be null",
+                                  e.getMessage());
+        });
+    }
+
+    @Test
+    public void testGet() {
+        Target target1 = createTarget("test1", HugeResourceType.VERTEX);
+        Target target2 = createTarget("test2", HugeResourceType.EDGE);
+
+        Assert.assertEquals(HugeResourceType.VERTEX,
+                            target1.resource().resourceType());
+        Assert.assertEquals(HugeResourceType.EDGE,
+                            target2.resource().resourceType());
+
+        target1 = api.get(target1.id());
+        target2 = api.get(target2.id());
+
+        Assert.assertEquals("test1", target1.name());
+        Assert.assertEquals(HugeResourceType.VERTEX,
+                            target1.resource().resourceType());
+
+        Assert.assertEquals("test2", target2.name());
+        Assert.assertEquals(HugeResourceType.EDGE,
+                            target2.resource().resourceType());
+    }
+
+    @Test
+    public void testList() {
+        createTarget("test1", HugeResourceType.VERTEX);
+        createTarget("test2", HugeResourceType.EDGE);
+        createTarget("test3", HugeResourceType.ALL);
+
+        List<Target> targets = api.list(-1);
+        Assert.assertEquals(3, targets.size());
+
+        targets.sort((t1, t2) -> t1.name().compareTo(t2.name()));
+        Assert.assertEquals("test1", targets.get(0).name());
+        Assert.assertEquals("test2", targets.get(1).name());
+        Assert.assertEquals("test3", targets.get(2).name());
+        Assert.assertEquals(HugeResourceType.VERTEX,
+                            targets.get(0).resource().resourceType());
+        Assert.assertEquals(HugeResourceType.EDGE,
+                            targets.get(1).resource().resourceType());
+        Assert.assertEquals(HugeResourceType.ALL,
+                            targets.get(2).resource().resourceType());
+
+        targets = api.list(1);
+        Assert.assertEquals(1, targets.size());
+
+        targets = api.list(2);
+        Assert.assertEquals(2, targets.size());
+
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            api.list(0);
+        }, e -> {
+            Assert.assertContains("Limit must be > 0 or == -1", e.getMessage());
+        });
+    }
+
+    @Test
+    public void testUpdate() {
+        Target target1 = createTarget("test1", HugeResourceType.VERTEX);
+        Target target2 = createTarget("test2", HugeResourceType.EDGE);
+
+        Assert.assertEquals(HugeResourceType.VERTEX,
+                            target1.resource().resourceType());
+        Assert.assertEquals(HugeResourceType.EDGE,
+                            target2.resource().resourceType());
+
+        target1.resources(new HugeResource(HugeResourceType.ALL));
+        Target updated = api.update(target1);
+        Assert.assertEquals(HugeResourceType.ALL,
+                            updated.resource().resourceType());
+        Assert.assertNotEquals(target1.updateTime(), updated.updateTime());
+
+        Assert.assertThrows(ServerException.class, () -> {
+            target2.name("test2-updated");
+            api.update(target2);
+        }, e -> {
+            Assert.assertContains("The name of target can't be updated",
+                                  e.getMessage());
+        });
+
+        Assert.assertThrows(ServerException.class, () -> {
+            Whitebox.setInternalState(target2, "id", "fake-id");
+            api.update(target2);
+        }, e -> {
+            Assert.assertContains("Invalid target id: fake-id",
+                                  e.getMessage());
+        });
+    }
+
+    @Test
+    public void testDelete() {
+        Target target1 = createTarget("test1", HugeResourceType.VERTEX);
+        Target target2 = createTarget("test2", HugeResourceType.EDGE);
+
+        Assert.assertEquals(2, api.list(-1).size());
+        api.delete(target1.id());
+
+        Assert.assertEquals(1, api.list(-1).size());
+        Assert.assertEquals(target2, api.list(-1).get(0));
+
+        api.delete(target2.id());
+        Assert.assertEquals(0, api.list(-1).size());
+
+        Assert.assertThrows(ServerException.class, () -> {
+            api.delete(target2.id());
+        }, e -> {
+            Assert.assertContains("Invalid target id:", e.getMessage());
+        });
+
+        Assert.assertThrows(ServerException.class, () -> {
+            api.delete("fake-id");
+        }, e -> {
+            Assert.assertContains("Invalid target id: fake-id",
+                                  e.getMessage());
+        });
+    }
+
+    protected static Target createTarget(String name, HugeResourceType res) {
+        Target target = new Target();
+        target.name(name);
+        target.graph("hugegraph");
+        target.url("127.0.0.1:8080");
+        target.resources(new HugeResource(res));
+        return api.create(target);
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/api/auth/TokenApiTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/auth/TokenApiTest.java
new file mode 100644
index 0000000..56d0afc
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/auth/TokenApiTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.auth;
+
+import java.util.List;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.baidu.hugegraph.client.RestClient;
+import com.baidu.hugegraph.exception.ServerException;
+import com.baidu.hugegraph.structure.auth.Login;
+import com.baidu.hugegraph.structure.auth.LoginResult;
+import com.baidu.hugegraph.structure.auth.TokenPayload;
+import com.baidu.hugegraph.structure.auth.User;
+import com.baidu.hugegraph.testutil.Assert;
+import com.baidu.hugegraph.testutil.Whitebox;
+
+public class TokenApiTest extends AuthApiTest {
+
+    private static TokenAPI tokenAPI;
+    private static LogoutAPI logoutAPI;
+    private static LoginAPI loginAPI;
+    private static UserAPI userAPI;
+
+    @BeforeClass
+    public static void init() {
+        tokenAPI = new TokenAPI(initClient(), GRAPH);
+        logoutAPI = new LogoutAPI(initClient(), GRAPH);
+        loginAPI = new LoginAPI(initClient(), GRAPH);
+        userAPI = new UserAPI(initClient(), GRAPH);
+    }
+
+    @AfterClass
+    public static void clear() {
+        List<User> users = userAPI.list(-1);
+        for (User user : users) {
+            if (user.name().equals("admin")) {
+                continue;
+            }
+            userAPI.delete(user.id());
+        }
+    }
+
+    @Test
+    public void testVerify() {
+        User user1 = new User();
+        user1.name("user1");
+        user1.password("p1");
+        User user = userAPI.create(user1);
+
+        Login login = new Login();
+        login.name("user1");
+        login.password("p1");
+        LoginResult result = loginAPI.login(login);
+        Assert.assertNotNull(result);
+        Assert.assertNotNull(result.token());
+
+        // Client will set Authentication Header use Basic
+        Assert.assertThrows(ServerException.class, () -> {
+            tokenAPI.verifyToken();
+        }, e -> {
+            Assert.assertContains("Only HTTP Bearer authentication is supported",
+                                  e.getMessage());
+        });
+
+        String token = result.token();
+        RestClient client = Whitebox.getInternalState(tokenAPI, "client");
+        client.setAuthContext("Bearer " + token);
+
+        TokenPayload payload = tokenAPI.verifyToken();
+        Assert.assertEquals("user1", payload.username());
+        Assert.assertEquals(user.id(), payload.userId());
+
+        client.setAuthContext("Bearer qweqwaasa");
+        Assert.assertThrows(ServerException.class, () -> {
+            tokenAPI.verifyToken();
+        }, e -> {
+            Assert.assertContains("Invalid token", e.getMessage());
+        });
+
+        RestClient client2 = Whitebox.getInternalState(logoutAPI, "client");
+        Assert.assertThrows(ServerException.class, () -> {
+            logoutAPI.logout();
+        }, e -> {
+            Assert.assertContains("Only HTTP Bearer authentication is supported",
+                                  e.getMessage());
+        });
+
+        client2.setAuthContext("Bearer " + token);
+        logoutAPI.logout();
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/api/auth/UserApiTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/auth/UserApiTest.java
new file mode 100644
index 0000000..cbf0a63
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/auth/UserApiTest.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.auth;
+
+import java.util.List;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.baidu.hugegraph.exception.ServerException;
+import com.baidu.hugegraph.structure.auth.User;
+import com.baidu.hugegraph.structure.auth.User.UserRole;
+import com.baidu.hugegraph.testutil.Assert;
+import com.baidu.hugegraph.testutil.Whitebox;
+
+public class UserApiTest extends AuthApiTest {
+
+    private static UserAPI api;
+
+    @BeforeClass
+    public static void init() {
+        api = new UserAPI(initClient(), GRAPH);
+    }
+
+    @AfterClass
+    public static void clear() {
+        List<User> users = api.list(-1);
+        for (User user : users) {
+            if (user.name().equals("admin")) {
+                continue;
+            }
+            api.delete(user.id());
+        }
+    }
+
+    @Override
+    @After
+    public void teardown() {
+        clear();
+    }
+
+    @Test
+    public void testCreate() {
+        User user1 = new User();
+        user1.name("user1");
+        user1.password("p1");
+        user1.email("user1@hugegraph.com");
+        user1.phone("123456789");
+        user1.avatar("image1.jpg");
+
+        User user2 = new User();
+        user2.name("user2");
+        user2.password("p2");
+        user2.email("user2@hugegraph.com");
+        user2.phone("1357924680");
+        user2.avatar("image2.jpg");
+
+        User result1 = api.create(user1);
+        User result2 = api.create(user2);
+
+        Assert.assertEquals("user1", result1.name());
+        Assert.assertNotEquals("p1", result1.password());
+        Assert.assertEquals("user1@hugegraph.com", result1.email());
+        Assert.assertEquals("123456789", result1.phone());
+        Assert.assertEquals("image1.jpg", result1.avatar());
+
+        Assert.assertEquals("user2", result2.name());
+        Assert.assertNotEquals("p2", result2.password());
+        Assert.assertEquals("user2@hugegraph.com", result2.email());
+        Assert.assertEquals("1357924680", result2.phone());
+        Assert.assertEquals("image2.jpg", result2.avatar());
+
+        Assert.assertThrows(ServerException.class, () -> {
+            api.create(new User());
+        }, e -> {
+            Assert.assertContains("The name of user can't be null",
+                                  e.getMessage());
+        });
+
+        Assert.assertThrows(ServerException.class, () -> {
+            User user3 = new User();
+            user3.name("test");
+            api.create(user3);
+        }, e -> {
+            Assert.assertContains("The password of user can't be null",
+                                  e.getMessage());
+        });
+
+        Assert.assertThrows(ServerException.class, () -> {
+            api.create(user1);
+        }, e -> {
+            Assert.assertContains("Can't save user", e.getMessage());
+            Assert.assertContains("that already exists", e.getMessage());
+        });
+
+        Assert.assertThrows(ServerException.class, () -> {
+            user1.name("admin");
+            api.create(user1);
+        }, e -> {
+            Assert.assertContains("Invalid user name 'admin'", e.getMessage());
+        });
+    }
+
+    @Test
+    public void testGet() {
+        User user1 = createUser("test1", "psw1");
+        User user2 = createUser("test2", "psw2");
+
+        Assert.assertEquals("test1", user1.name());
+        Assert.assertEquals("test2", user2.name());
+
+        user1 = api.get(user1.id());
+        user2 = api.get(user2.id());
+
+        Assert.assertEquals("test1", user1.name());
+        Assert.assertEquals("test2", user2.name());
+    }
+
+    @Test
+    public void testGetUserRole() {
+        User user1 = createUser("test1", "psw1");
+        User user2 = createUser("test2", "psw2");
+
+        Assert.assertEquals("test1", user1.name());
+        Assert.assertEquals("test2", user2.name());
+
+        UserRole role1 = api.getUserRole(user1.id());
+        UserRole role2 = api.getUserRole(user2.id());
+
+        Assert.assertEquals("{\"roles\":{}}", role1.toString());
+        Assert.assertEquals("{\"roles\":{}}", role2.toString());
+    }
+
+    @Test
+    public void testList() {
+        createUser("test1", "psw1");
+        createUser("test2", "psw2");
+        createUser("test3", "psw3");
+
+        List<User> users = api.list(-1);
+        Assert.assertEquals(4, users.size());
+
+        users.sort((t1, t2) -> t1.name().compareTo(t2.name()));
+        Assert.assertEquals("admin", users.get(0).name());
+        Assert.assertEquals("test1", users.get(1).name());
+        Assert.assertEquals("test2", users.get(2).name());
+        Assert.assertEquals("test3", users.get(3).name());
+
+        users = api.list(1);
+        Assert.assertEquals(1, users.size());
+
+        users = api.list(2);
+        Assert.assertEquals(2, users.size());
+
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            api.list(0);
+        }, e -> {
+            Assert.assertContains("Limit must be > 0 or == -1", e.getMessage());
+        });
+    }
+
+    @Test
+    public void testUpdate() {
+        User user1 = createUser("test1", "psw1");
+        User user2 = createUser("test2", "psw2");
+
+        Assert.assertEquals("test@hugegraph.com", user1.email());
+        Assert.assertEquals("16812345678", user1.phone());
+        Assert.assertEquals("image.jpg", user1.avatar());
+
+        String oldPassw = user1.password();
+        Assert.assertNotEquals("psw1", oldPassw);
+
+        user1.password("psw-udated");
+        user1.email("test_updated@hugegraph.com");
+        user1.phone("1357924680");
+        user1.avatar("image-updated.jpg");
+
+        User updated = api.update(user1);
+        Assert.assertNotEquals(oldPassw, updated.password());
+        Assert.assertEquals("test_updated@hugegraph.com", updated.email());
+        Assert.assertEquals("1357924680", updated.phone());
+        Assert.assertEquals("image-updated.jpg", updated.avatar());
+        Assert.assertNotEquals(user1.updateTime(), updated.updateTime());
+
+        Assert.assertThrows(ServerException.class, () -> {
+            user2.name("test2-updated");
+            api.update(user2);
+        }, e -> {
+            Assert.assertContains("The name of user can't be updated",
+                                  e.getMessage());
+        });
+
+        Assert.assertThrows(ServerException.class, () -> {
+            Whitebox.setInternalState(user2, "id", "fake-id");
+            api.update(user2);
+        }, e -> {
+            Assert.assertContains("Invalid user id: fake-id",
+                                  e.getMessage());
+        });
+    }
+
+    @Test
+    public void testDelete() {
+        User user1 = createUser("test1", "psw1");
+        User user2 = createUser("test2", "psw2");
+
+        List<User> users = api.list(-1);
+        Assert.assertEquals(3, users.size());
+        Assert.assertTrue(users.contains(user1));
+        Assert.assertTrue(users.contains(user2));
+        api.delete(user1.id());
+
+        users = api.list(-1);
+        Assert.assertEquals(2, users.size());
+        Assert.assertFalse(users.contains(user1));
+        Assert.assertTrue(users.contains(user2));
+
+        api.delete(user2.id());
+        users = api.list(-1);
+        Assert.assertEquals(1, users.size());
+        Assert.assertEquals("admin", users.get(0).name());
+
+        User admin = users.get(0);
+        Assert.assertThrows(ServerException.class, () -> {
+            api.delete(admin.id());
+        }, e -> {
+            Assert.assertContains("Can't delete user 'admin'", e.getMessage());
+        });
+
+        Assert.assertThrows(ServerException.class, () -> {
+            api.delete(user2.id());
+        }, e -> {
+            Assert.assertContains("Invalid user id:", e.getMessage());
+        });
+
+        Assert.assertThrows(ServerException.class, () -> {
+            api.delete("fake-id");
+        }, e -> {
+            Assert.assertContains("Invalid user id: fake-id", e.getMessage());
+        });
+    }
+
+    protected static User createUser(String name, String password) {
+        User user = new User();
+        user.name(name);
+        user.password(password);
+        user.email("test@hugegraph.com");
+        user.phone("16812345678");
+        user.avatar("image.jpg");
+        return api.create(user);
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/api/traverser/AllShortestPathsApiTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/traverser/AllShortestPathsApiTest.java
new file mode 100644
index 0000000..c5c66a9
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/traverser/AllShortestPathsApiTest.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.traverser;
+
+import java.util.List;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.baidu.hugegraph.exception.ServerException;
+import com.baidu.hugegraph.structure.constant.Direction;
+import com.baidu.hugegraph.structure.constant.T;
+import com.baidu.hugegraph.structure.graph.Path;
+import com.baidu.hugegraph.structure.graph.Vertex;
+import com.baidu.hugegraph.testutil.Assert;
+import com.google.common.collect.ImmutableList;
+
+public class AllShortestPathsApiTest extends TraverserApiTest {
+
+    @BeforeClass
+    public static void initShortestPathGraph() {
+        schema().vertexLabel("node")
+                .useCustomizeNumberId()
+                .ifNotExist()
+                .create();
+
+        schema().edgeLabel("link")
+                .sourceLabel("node").targetLabel("node")
+                .ifNotExist()
+                .create();
+
+        Vertex v1 = graph().addVertex(T.label, "node", T.id, 1);
+        Vertex v2 = graph().addVertex(T.label, "node", T.id, 2);
+        Vertex v3 = graph().addVertex(T.label, "node", T.id, 3);
+        Vertex v4 = graph().addVertex(T.label, "node", T.id, 4);
+        Vertex v5 = graph().addVertex(T.label, "node", T.id, 5);
+        Vertex v6 = graph().addVertex(T.label, "node", T.id, 6);
+        Vertex v7 = graph().addVertex(T.label, "node", T.id, 7);
+        Vertex v8 = graph().addVertex(T.label, "node", T.id, 8);
+        Vertex v9 = graph().addVertex(T.label, "node", T.id, 9);
+        Vertex v10 = graph().addVertex(T.label, "node", T.id, 10);
+        Vertex v11 = graph().addVertex(T.label, "node", T.id, 11);
+        Vertex v12 = graph().addVertex(T.label, "node", T.id, 12);
+        Vertex v13 = graph().addVertex(T.label, "node", T.id, 13);
+        Vertex v14 = graph().addVertex(T.label, "node", T.id, 14);
+        Vertex v15 = graph().addVertex(T.label, "node", T.id, 15);
+        Vertex v16 = graph().addVertex(T.label, "node", T.id, 16);
+        Vertex v17 = graph().addVertex(T.label, "node", T.id, 17);
+        Vertex v18 = graph().addVertex(T.label, "node", T.id, 18);
+        Vertex v19 = graph().addVertex(T.label, "node", T.id, 19);
+        Vertex v20 = graph().addVertex(T.label, "node", T.id, 20);
+        Vertex v21 = graph().addVertex(T.label, "node", T.id, 21);
+        Vertex v22 = graph().addVertex(T.label, "node", T.id, 22);
+        Vertex v23 = graph().addVertex(T.label, "node", T.id, 23);
+        Vertex v24 = graph().addVertex(T.label, "node", T.id, 24);
+        Vertex v25 = graph().addVertex(T.label, "node", T.id, 25);
+        Vertex v26 = graph().addVertex(T.label, "node", T.id, 26);
+        Vertex v27 = graph().addVertex(T.label, "node", T.id, 27);
+        Vertex v28 = graph().addVertex(T.label, "node", T.id, 28);
+
+        // Path length 5
+        v1.addEdge("link", v2);
+        v2.addEdge("link", v3);
+        v3.addEdge("link", v4);
+        v4.addEdge("link", v5);
+        v5.addEdge("link", v6);
+
+        v1.addEdge("link", v25);
+        v25.addEdge("link", v26);
+        v26.addEdge("link", v27);
+        v27.addEdge("link", v28);
+        v28.addEdge("link", v6);
+
+        // Path length 4
+        v1.addEdge("link", v7);
+        v7.addEdge("link", v8);
+        v8.addEdge("link", v9);
+        v9.addEdge("link", v6);
+
+        // Path length 3
+        v1.addEdge("link", v10);
+        v10.addEdge("link", v11);
+        v11.addEdge("link", v6);
+
+        v1.addEdge("link", v19);
+        v19.addEdge("link", v20);
+        v20.addEdge("link", v6);
+
+        v1.addEdge("link", v21);
+        v21.addEdge("link", v22);
+        v22.addEdge("link", v6);
+
+        v1.addEdge("link", v23);
+        v23.addEdge("link", v24);
+        v24.addEdge("link", v6);
+
+        // Add other 3 neighbor for v7
+        v7.addEdge("link", v12);
+        v7.addEdge("link", v13);
+        v7.addEdge("link", v14);
+
+        // Add other 4 neighbor for v10
+        v10.addEdge("link", v15);
+        v10.addEdge("link", v16);
+        v10.addEdge("link", v17);
+        v10.addEdge("link", v18);
+    }
+
+    @Test
+    public void testAllShortestPath() {
+        List<Path> paths = allShortestPathsAPI.get(1, 6, Direction.BOTH,
+                                                   null, 6, -1L, 0L, -1L);
+        Assert.assertEquals(4, paths.size());
+        List<List<Object>> expected = ImmutableList.of(
+                ImmutableList.of(1, 10, 11, 6),
+                ImmutableList.of(1, 19, 20, 6),
+                ImmutableList.of(1, 21, 22, 6),
+                ImmutableList.of(1, 23, 24, 6)
+        );
+        for (Path path : paths){
+            Assert.assertTrue(expected.contains(path.objects()));
+        }
+    }
+
+    @Test
+    public void testAllShortestPathWithLabel() {
+        List<Path> paths = allShortestPathsAPI.get(1, 6, Direction.BOTH,
+                                                   "link", 6, -1L, 0L,
+                                                   -1L);
+        Assert.assertEquals(4, paths.size());
+        List<List<Object>> expected = ImmutableList.of(
+                ImmutableList.of(1, 10, 11, 6),
+                ImmutableList.of(1, 19, 20, 6),
+                ImmutableList.of(1, 21, 22, 6),
+                ImmutableList.of(1, 23, 24, 6)
+        );
+        for (Path path : paths){
+            Assert.assertTrue(expected.contains(path.objects()));
+        }
+    }
+
+    @Test
+    public void testAllShortestPathWithDegree() {
+        List<Path> paths = allShortestPathsAPI.get(1, 6, Direction.OUT,
+                                                   null, 6, 1L, 0L, -1L);
+        /*
+         * Following results can be guaranteed in RocksDB backend,
+         * but different results exist in table type backend(like Cassandra).
+         */
+        Assert.assertEquals(1, paths.size());
+        List<Object> expected = ImmutableList.of(1, 2, 3, 4, 5, 6);
+        Assert.assertEquals(expected, paths.iterator().next().objects());
+    }
+
+    @Test
+    public void testAllShortestPathWithCapacity() {
+        List<Path> paths = allShortestPathsAPI.get(1, 6, Direction.BOTH,
+                                                   null, 6, -1L, 0L, -1L);
+        Assert.assertEquals(4, paths.size());
+        List<List<Object>> expected = ImmutableList.of(
+                ImmutableList.of(1, 10, 11, 6),
+                ImmutableList.of(1, 19, 20, 6),
+                ImmutableList.of(1, 21, 22, 6),
+                ImmutableList.of(1, 23, 24, 6)
+        );
+        for (Path path : paths){
+            Assert.assertTrue(expected.contains(path.objects()));
+        }
+        Assert.assertThrows(ServerException.class, () -> {
+            allShortestPathsAPI.get(1, 6, Direction.BOTH, null, 6, 6,
+                                    0L, 10L);
+        }, e -> {
+            String expect = "Exceed capacity '10' while finding shortest path";
+            Assert.assertContains(expect, e.getMessage());
+        });
+    }
+
+    @Test
+    public void testAllShortestPathWithMaxDepth() {
+        List<Path> paths = allShortestPathsAPI.get(14, 6, Direction.BOTH,
+                                                   null, 4, 5L, 0L, 19L);
+        Assert.assertEquals(1, paths.size());
+        Path path = paths.get(0);
+        Assert.assertEquals(ImmutableList.of(14, 7, 8, 9, 6), path.objects());
+
+        paths = allShortestPathsAPI.get(14, 6, Direction.BOTH,
+                                        null, 3, 5L, 0L, 19L);
+        Assert.assertEquals(0, paths.size());
+    }
+
+    @Test
+    public void testAllShortestPathWithIllegalArgs() {
+        // The max depth shouldn't be 0 or negative
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            allShortestPathsAPI.get("a", "b", Direction.BOTH,
+                                    null, -1, 1L, 0L, 2L);
+        });
+
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            shortestPathAPI.get("a", "b", Direction.BOTH,
+                                null, 0, 1L, 0L, 2L);
+        });
+
+        // The degree shouldn't be 0 or negative but NO_LIMIT(-1)
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            allShortestPathsAPI.get("a", "b", Direction.BOTH,
+                                    null, 5, 0L, 0L, 2L);
+        });
+
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            allShortestPathsAPI.get("a", "b", Direction.BOTH,
+                                    null, 5, -3L, 0L, 2L);
+        });
+
+        // The skipped degree shouldn't be negative
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            allShortestPathsAPI.get("a", "b", Direction.BOTH,
+                                    null, 5, 1L, -1L, 2L);
+        });
+
+        // The skipped degree shouldn't be >= capacity
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            allShortestPathsAPI.get("a", "b", Direction.BOTH,
+                                    null, 5, 1L, 2L, 2L);
+        });
+
+        // The skipped degree shouldn't be < degree
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            allShortestPathsAPI.get("a", "b", Direction.BOTH,
+                                    null, 5, 3L, 2L, 10L);
+        });
+
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            allShortestPathsAPI.get("a", "b", Direction.BOTH,
+                                    null, 5, -1L, 2L, 10L);
+        });
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/api/traverser/CommonTraverserApiTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/traverser/CommonTraverserApiTest.java
new file mode 100644
index 0000000..77574e1
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/traverser/CommonTraverserApiTest.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.traverser;
+
+import static com.baidu.hugegraph.structure.constant.Traverser.DEFAULT_PAGE_LIMIT;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.baidu.hugegraph.api.BaseApiTest;
+import com.baidu.hugegraph.exception.ServerException;
+import com.baidu.hugegraph.structure.constant.Direction;
+import com.baidu.hugegraph.structure.graph.Edge;
+import com.baidu.hugegraph.structure.graph.Edges;
+import com.baidu.hugegraph.structure.graph.Path;
+import com.baidu.hugegraph.structure.graph.Shard;
+import com.baidu.hugegraph.structure.graph.Vertex;
+import com.baidu.hugegraph.structure.graph.Vertices;
+import com.baidu.hugegraph.structure.traverser.CrosspointsRequest;
+import com.baidu.hugegraph.structure.traverser.CustomizedCrosspoints;
+import com.baidu.hugegraph.testutil.Assert;
+import com.baidu.hugegraph.testutil.Utils;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+public class CommonTraverserApiTest extends TraverserApiTest {
+
+    @BeforeClass
+    public static void prepareSchemaAndGraph() {
+        BaseApiTest.initPropertyKey();
+        BaseApiTest.initVertexLabel();
+        BaseApiTest.initEdgeLabel();
+        BaseApiTest.initIndexLabel();
+        BaseApiTest.initVertex();
+        BaseApiTest.initEdge();
+    }
+
+    @Test
+    public void testCrosspoints() {
+        Object markoId = getVertexId("person", "name", "marko");
+        Object joshId = getVertexId("person", "name", "josh");
+        Object lopId = getVertexId("software", "name", "lop");
+        Object peterId = getVertexId("person", "name", "peter");
+
+        List<Path> paths = crosspointsAPI.get(markoId, peterId, Direction.OUT,
+                                              null, 3, -1L, -1L, 10);
+        Assert.assertEquals(2, paths.size());
+        Path crosspoint1 = new Path(lopId,
+                                    ImmutableList.of(markoId, lopId, peterId));
+        Path crosspoint2 = new Path(lopId, ImmutableList.of(markoId, joshId,
+                                                            lopId, peterId));
+
+        List<Path> crosspoints = ImmutableList.of(crosspoint1, crosspoint2);
+        Assert.assertTrue(crosspoints.contains(paths.get(0)));
+        Assert.assertTrue(crosspoints.contains(paths.get(1)));
+    }
+
+    @Test
+    public void testCrosspointsWithCapacity() {
+        Object markoId = getVertexId("person", "name", "marko");
+        Object peterId = getVertexId("person", "name", "peter");
+
+        Assert.assertThrows(ServerException.class, () -> {
+            crosspointsAPI.get(markoId, peterId, Direction.OUT,
+                               null, 3, -1L, 2L, 10);
+        }, e -> {
+            String expect = "Exceed capacity '2' while finding paths";
+            Assert.assertContains(expect, e.getMessage());
+        });
+    }
+
+    @Test
+    public void testCustomizedCrosspoints() {
+        Object lopId = getVertexId("software", "name", "lop");
+        Object joshId = getVertexId("person", "name", "josh");
+        Object rippleId = getVertexId("software", "name", "ripple");
+
+        CrosspointsRequest.Builder builder = CrosspointsRequest.builder();
+        builder.sources().ids(lopId, rippleId);
+        builder.pathPatterns().steps().direction(Direction.IN)
+                                      .labels("created").degree(-1);
+        builder.withPath(true).withVertex(true).capacity(-1).limit(-1);
+
+        CustomizedCrosspoints customizedCrosspoints =
+                              customizedCrosspointsAPI.post(builder.build());
+        List<Object> crosspoints = customizedCrosspoints.crosspoints();
+        Assert.assertEquals(1, crosspoints.size());
+        Assert.assertEquals(joshId, crosspoints.get(0));
+
+        List<Path> paths = customizedCrosspoints.paths();
+        Assert.assertEquals(2, paths.size());
+
+        List<Object> path1 = ImmutableList.of(rippleId, joshId);
+        List<Object> path2 = ImmutableList.of(lopId, joshId);
+        List<List<Object>> expectedPaths = ImmutableList.of(path1, path2);
+        Assert.assertTrue(expectedPaths.contains(paths.get(0).objects()));
+        Assert.assertTrue(expectedPaths.contains(paths.get(1).objects()));
+
+        Set<?> vertices = customizedCrosspoints.vertices().stream()
+                                               .map(Vertex::id)
+                                               .collect(Collectors.toSet());
+        List<Object> expectedVids = ImmutableList.of(rippleId, joshId, lopId);
+        Assert.assertTrue(expectedVids.containsAll(vertices));
+    }
+
+    @Test
+    public void testVertices() {
+        Object markoId = getVertexId("person", "name", "marko");
+        Object vadasId = getVertexId("person", "name", "vadas");
+        Object joshId = getVertexId("person", "name", "josh");
+        Object peterId = getVertexId("person", "name", "peter");
+        Object lopId = getVertexId("software", "name", "lop");
+        Object rippleId = getVertexId("software", "name", "ripple");
+
+        List<Object> ids = ImmutableList.of(markoId, vadasId, joshId,
+                                            peterId, lopId, rippleId);
+        List<Vertex> vertices = verticesAPI.list(ids);
+
+        Assert.assertEquals(6, vertices.size());
+
+        Assert.assertEquals(markoId, vertices.get(0).id());
+        Assert.assertEquals(vadasId, vertices.get(1).id());
+        Assert.assertEquals(joshId, vertices.get(2).id());
+        Assert.assertEquals(peterId, vertices.get(3).id());
+        Assert.assertEquals(lopId, vertices.get(4).id());
+        Assert.assertEquals(rippleId, vertices.get(5).id());
+
+        Map<String, Object> props = ImmutableMap.of("name", "josh",
+                                                    "city", "Beijing",
+                                                    "age", 32);
+        Assert.assertEquals(props, vertices.get(2).properties());
+    }
+
+    @Test
+    public void testEdges() {
+        String date2012Id = getEdgeId("knows", "date", "2012-01-10");
+        String date2013Id = getEdgeId("knows", "date", "2013-01-10");
+        String date2014Id = getEdgeId("created", "date", "2014-01-10");
+        String date2015Id = getEdgeId("created", "date", "2015-01-10");
+        String date2016Id = getEdgeId("created", "date", "2016-01-10");
+        String date2017Id = getEdgeId("created", "date", "2017-01-10");
+
+        List<String> ids = ImmutableList.of(date2012Id, date2013Id, date2014Id,
+                                            date2015Id, date2016Id, date2017Id);
+
+        List<Edge> edges = edgesAPI.list(ids);
+
+        Assert.assertEquals(6, edges.size());
+
+        Assert.assertEquals(date2012Id, edges.get(0).id());
+        Assert.assertEquals(date2013Id, edges.get(1).id());
+        Assert.assertEquals(date2014Id, edges.get(2).id());
+        Assert.assertEquals(date2015Id, edges.get(3).id());
+        Assert.assertEquals(date2016Id, edges.get(4).id());
+        Assert.assertEquals(date2017Id, edges.get(5).id());
+
+        String date = Utils.formatDate("2014-01-10");
+        Map<String, Object> props = ImmutableMap.of("date", date,
+                                                    "city", "Shanghai");
+        Assert.assertEquals(props, edges.get(2).properties());
+    }
+
+    @Test
+    public void testScanVertex() {
+        List<Shard> shards = verticesAPI.shards(1 * 1024 * 1024);
+        List<Vertex> vertices = new LinkedList<>();
+        for (Shard shard : shards) {
+            Vertices results = verticesAPI.scan(shard, null, 0L);
+            vertices.addAll(ImmutableList.copyOf(results.results()));
+            Assert.assertNull(results.page());
+        }
+        Assert.assertEquals(6, vertices.size());
+    }
+
+    @Test
+    public void testScanVertexInPaging() {
+        List<Shard> shards = verticesAPI.shards(1 * 1024 * 1024);
+        List<Vertex> vertices = new LinkedList<>();
+        for (Shard shard : shards) {
+            String page = "";
+            while (page != null) {
+                Vertices results = verticesAPI.scan(shard, page, DEFAULT_PAGE_LIMIT);
+                vertices.addAll(ImmutableList.copyOf(results.results()));
+                page = results.page();
+            }
+        }
+        Assert.assertEquals(6, vertices.size());
+    }
+
+    @Test
+    public void testScanVertexInPagingWithNegativeLimit() {
+        List<Shard> shards = verticesAPI.shards(1 * 1024 * 1024);
+        for (Shard shard : shards) {
+            String page = "";
+            Assert.assertThrows(ServerException.class, () -> {
+                verticesAPI.scan(shard, page, -1);
+            }, e -> {
+                Assert.assertContains("Invalid limit -1", e.getMessage());
+            });
+        }
+    }
+
+    @Test
+    public void testScanVertexWithSplitSizeLt1MB() {
+        Assert.assertThrows(ServerException.class, () -> {
+            verticesAPI.shards(1 * 1024 * 1024 - 1);
+        }, e -> {
+            String expect = "The split-size must be >= 1048576 bytes, " +
+                            "but got 1048575";
+            Assert.assertContains(expect, e.getMessage());
+        });
+    }
+
+    @Test
+    public void testScanEdge() {
+        List<Shard> shards = edgesAPI.shards(1 * 1024 * 1024);
+        List<Edge> edges = new LinkedList<>();
+        for (Shard shard : shards) {
+            Edges results = edgesAPI.scan(shard, null, 0L);
+            Assert.assertNull(results.page());
+            edges.addAll(ImmutableList.copyOf(results.results()));
+        }
+        Assert.assertEquals(6, edges.size());
+    }
+
+    @Test
+    public void testScanEdgeInPaging() {
+        List<Shard> shards = edgesAPI.shards(1 * 1024 * 1024);
+        List<Edge> edges = new LinkedList<>();
+        for (Shard shard : shards) {
+            String page = "";
+            while (page != null) {
+                Edges results = edgesAPI.scan(shard, page, DEFAULT_PAGE_LIMIT);
+                edges.addAll(ImmutableList.copyOf(results.results()));
+                page = results.page();
+            }
+        }
+        Assert.assertEquals(6, edges.size());
+    }
+
+    @Test
+    public void testScanEdgeInPagingWithNegativeLimit() {
+        List<Shard> shards = edgesAPI.shards(1 * 1024 * 1024);
+        for (Shard shard : shards) {
+            String page = "";
+            Assert.assertThrows(ServerException.class, () -> {
+                edgesAPI.scan(shard, page, -1);
+            }, e -> {
+                String expect = "Invalid limit -1";
+                Assert.assertContains(expect, e.getMessage());
+            });
+        }
+    }
+
+    @Test
+    public void testScanEdgeWithSplitSizeLt1MB() {
+        Assert.assertThrows(ServerException.class, () -> {
+            edgesAPI.shards(1 * 1024 * 1024 - 1);
+        });
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/api/traverser/CountApiTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/traverser/CountApiTest.java
new file mode 100644
index 0000000..0001ac2
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/traverser/CountApiTest.java
@@ -0,0 +1,497 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.traverser;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.baidu.hugegraph.exception.ServerException;
+import com.baidu.hugegraph.structure.constant.Direction;
+import com.baidu.hugegraph.structure.constant.T;
+import com.baidu.hugegraph.structure.graph.Vertex;
+import com.baidu.hugegraph.structure.traverser.CountRequest;
+import com.baidu.hugegraph.testutil.Assert;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+public class CountApiTest extends TraverserApiTest {
+
+    @BeforeClass
+    public static void initGraph() {
+        schema().propertyKey("time")
+                .asDate()
+                .ifNotExist()
+                .create();
+
+        schema().propertyKey("weight")
+                .asDouble()
+                .ifNotExist()
+                .create();
+
+        schema().vertexLabel("node")
+                .useCustomizeStringId()
+                .ifNotExist()
+                .create();
+
+        schema().edgeLabel("link")
+                .sourceLabel("node").targetLabel("node")
+                .properties("time")
+                .multiTimes().sortKeys("time")
+                .ifNotExist()
+                .create();
+
+        schema().edgeLabel("relateTo")
+                .sourceLabel("node").targetLabel("node")
+                .properties("weight")
+                .ifNotExist()
+                .create();
+
+        Vertex va = graph().addVertex(T.label, "node", T.id, "A");
+        Vertex vb = graph().addVertex(T.label, "node", T.id, "B");
+        Vertex vc = graph().addVertex(T.label, "node", T.id, "C");
+        Vertex vd = graph().addVertex(T.label, "node", T.id, "D");
+        Vertex ve = graph().addVertex(T.label, "node", T.id, "E");
+        Vertex vf = graph().addVertex(T.label, "node", T.id, "F");
+        Vertex vg = graph().addVertex(T.label, "node", T.id, "G");
+        Vertex vh = graph().addVertex(T.label, "node", T.id, "H");
+        Vertex vi = graph().addVertex(T.label, "node", T.id, "I");
+        Vertex vj = graph().addVertex(T.label, "node", T.id, "J");
+        Vertex vk = graph().addVertex(T.label, "node", T.id, "K");
+        Vertex vl = graph().addVertex(T.label, "node", T.id, "L");
+        Vertex vm = graph().addVertex(T.label, "node", T.id, "M");
+        Vertex vn = graph().addVertex(T.label, "node", T.id, "N");
+        Vertex vo = graph().addVertex(T.label, "node", T.id, "O");
+        Vertex vp = graph().addVertex(T.label, "node", T.id, "P");
+        Vertex vq = graph().addVertex(T.label, "node", T.id, "Q");
+        Vertex vr = graph().addVertex(T.label, "node", T.id, "R");
+        Vertex vs = graph().addVertex(T.label, "node", T.id, "S");
+        Vertex vt = graph().addVertex(T.label, "node", T.id, "T");
+        Vertex vu = graph().addVertex(T.label, "node", T.id, "U");
+        Vertex vv = graph().addVertex(T.label, "node", T.id, "V");
+        Vertex vw = graph().addVertex(T.label, "node", T.id, "W");
+        Vertex vx = graph().addVertex(T.label, "node", T.id, "X");
+        Vertex vy = graph().addVertex(T.label, "node", T.id, "Y");
+        Vertex vz = graph().addVertex(T.label, "node", T.id, "Z");
+
+        /*
+         *
+         *             c -----> f
+         *            ^
+         *           / d -----> g
+         *          / ^
+         *         / /
+         *        b ---> e -----> h
+         *       ^
+         *      /     j <----- m
+         *     /     /
+         *    /     /
+         *   /     <
+         * a <--- i <--- k <--- n
+         *   .     ^
+         *    .     \
+         *     .     \
+         *      .     l <------ o
+         *       >
+         *        p ...> q ...> v
+         *          ...> r ...> w
+         *          ...> s ...> x
+         *          ...> t ...> y
+         *          ...> u ...> z
+         *
+         * Description:
+         * 1. ">","<","^" means arrow
+         * 2. "---" means "link" edge
+         * 3. "..." means "relateTo" edge
+         *
+         */
+        va.addEdge("link", vb, "time", "2020-01-01");
+
+        vb.addEdge("link", vc, "time", "2020-01-02");
+        vb.addEdge("link", vd, "time", "2020-01-03");
+        vb.addEdge("link", ve, "time", "2020-01-04");
+
+        vc.addEdge("link", vf, "time", "2020-01-05");
+        vd.addEdge("link", vg, "time", "2020-01-06");
+        ve.addEdge("link", vh, "time", "2020-01-07");
+
+        vi.addEdge("link", va, "time", "2020-01-08");
+
+        vj.addEdge("link", vi, "time", "2020-01-09");
+        vk.addEdge("link", vi, "time", "2020-01-10");
+        vl.addEdge("link", vi, "time", "2020-01-11");
+
+        vm.addEdge("link", vj, "time", "2020-01-12");
+        vn.addEdge("link", vk, "time", "2020-01-13");
+        vo.addEdge("link", vl, "time", "2020-01-14");
+
+        va.addEdge("relateTo", vp, "weight", 0.0D);
+
+        vp.addEdge("relateTo", vq, "weight", 0.1D);
+        vp.addEdge("relateTo", vr, "weight", 0.2D);
+        vp.addEdge("relateTo", vs, "weight", 0.3D);
+        vp.addEdge("relateTo", vt, "weight", 0.4D);
+        vp.addEdge("relateTo", vu, "weight", 0.5D);
+
+        vq.addEdge("relateTo", vv, "weight", 0.6D);
+        vr.addEdge("relateTo", vw, "weight", 0.7D);
+        vs.addEdge("relateTo", vx, "weight", 0.8D);
+        vt.addEdge("relateTo", vy, "weight", 0.9D);
+        vu.addEdge("relateTo", vz, "weight", 1.0D);
+    }
+
+    @Test
+    public void testCount() {
+        CountRequest.Builder builder = CountRequest.builder();
+        builder.source("A").containsTraversed(false);
+        builder.steps().direction(Direction.OUT);
+        builder.steps().direction(Direction.OUT);
+        builder.steps().direction(Direction.OUT);
+        CountRequest request = builder.build();
+
+        long count = countAPI.post(request);
+        Assert.assertEquals(8L, count);
+    }
+
+    @Test
+    public void testCountWithContainsTraversed() {
+        CountRequest.Builder builder = CountRequest.builder();
+        builder.source("A").containsTraversed(true);
+        builder.steps().direction(Direction.OUT);
+        builder.steps().direction(Direction.OUT);
+        builder.steps().direction(Direction.OUT);
+        CountRequest request = builder.build();
+
+        long count = countAPI.post(request);
+        Assert.assertEquals(19L, count);
+    }
+
+    @Test
+    public void testCountWithDirection() {
+        CountRequest.Builder builder = CountRequest.builder();
+        builder.source("A").containsTraversed(true);
+        builder.steps().direction(Direction.OUT);
+        builder.steps().direction(Direction.OUT);
+        builder.steps().direction(Direction.OUT);
+        CountRequest request = builder.build();
+
+        long count = countAPI.post(request);
+        Assert.assertEquals(19L, count);
+
+        builder = CountRequest.builder();
+        builder.source("A").containsTraversed(false);
+        builder.steps().direction(Direction.OUT);
+        builder.steps().direction(Direction.OUT);
+        builder.steps().direction(Direction.OUT);
+        request = builder.build();
+
+        count = countAPI.post(request);
+        Assert.assertEquals(8L, count);
+
+        builder = CountRequest.builder();
+        builder.source("A").containsTraversed(false);
+        builder.steps().direction(Direction.IN);
+        builder.steps().direction(Direction.IN);
+        builder.steps().direction(Direction.IN);
+        request = builder.build();
+
+        count = countAPI.post(request);
+        Assert.assertEquals(3L, count);
+
+        builder = CountRequest.builder();
+        builder.source("A").containsTraversed(true);
+        builder.steps().direction(Direction.IN);
+        builder.steps().direction(Direction.IN);
+        builder.steps().direction(Direction.IN);
+        request = builder.build();
+
+        count = countAPI.post(request);
+        Assert.assertEquals(8L, count);
+    }
+
+    @Test
+    public void testCountWithLabel() {
+        CountRequest.Builder builder = CountRequest.builder();
+        builder.source("A").containsTraversed(false);
+        builder.steps().direction(Direction.OUT)
+               .labels(ImmutableList.of("link"));
+        builder.steps().direction(Direction.OUT)
+               .labels(ImmutableList.of("link"));
+        builder.steps().direction(Direction.OUT)
+               .labels(ImmutableList.of("link"));
+        CountRequest request = builder.build();
+
+        long count = countAPI.post(request);
+        Assert.assertEquals(3L, count);
+
+        builder = CountRequest.builder();
+        builder.source("A").containsTraversed(true);
+        builder.steps().direction(Direction.OUT)
+               .labels(ImmutableList.of("link"));
+        builder.steps().direction(Direction.OUT)
+               .labels(ImmutableList.of("link"));
+        builder.steps().direction(Direction.OUT)
+               .labels(ImmutableList.of("link"));
+        request = builder.build();
+
+        count = countAPI.post(request);
+        Assert.assertEquals(8L, count);
+    }
+
+    @Test
+    public void testCountWithProperties() {
+        CountRequest.Builder builder = CountRequest.builder();
+        builder.source("A").containsTraversed(false);
+        builder.steps().direction(Direction.OUT)
+               .labels(ImmutableList.of("link"))
+               .properties("time", "P.lt(\"2020-01-06\")");
+        builder.steps().direction(Direction.OUT)
+               .labels(ImmutableList.of("link"))
+               .properties("time", "P.lt(\"2020-01-06\")");
+        builder.steps().direction(Direction.OUT)
+               .labels(ImmutableList.of("link"))
+               .properties("time", "P.lt(\"2020-01-06\")");
+        CountRequest request = builder.build();
+
+        long count = countAPI.post(request);
+        Assert.assertEquals(1L, count);
+
+        builder = CountRequest.builder();
+        builder.source("A").containsTraversed(true);
+        builder.steps().direction(Direction.OUT)
+               .labels(ImmutableList.of("link"))
+               .properties("time", "P.lt(\"2020-01-06\")");
+        builder.steps().direction(Direction.OUT)
+               .labels(ImmutableList.of("link"))
+               .properties("time", "P.lt(\"2020-01-06\")");
+        builder.steps().direction(Direction.OUT)
+               .labels(ImmutableList.of("link"))
+               .properties("time", "P.lt(\"2020-01-06\")");
+        request = builder.build();
+
+        count = countAPI.post(request);
+        Assert.assertEquals(6L, count);
+
+        builder = CountRequest.builder();
+        builder.source("A").containsTraversed(false);
+        builder.steps().direction(Direction.OUT)
+               .labels(ImmutableList.of("link"));
+        builder.steps().direction(Direction.OUT)
+               .labels(ImmutableList.of("link"))
+               .properties("time", "P.gt(\"2020-01-03\")");
+        builder.steps().direction(Direction.OUT)
+               .labels(ImmutableList.of("link"));
+        request = builder.build();
+
+        count = countAPI.post(request);
+        Assert.assertEquals(1L, count);
+
+        builder = CountRequest.builder();
+        builder.source("A").containsTraversed(true);
+        builder.steps().direction(Direction.OUT)
+               .labels(ImmutableList.of("link"));
+        builder.steps().direction(Direction.OUT)
+               .labels(ImmutableList.of("link"))
+               .properties("time", "P.gt(\"2020-01-03\")");
+        builder.steps().direction(Direction.OUT)
+               .labels(ImmutableList.of("link"));
+        request = builder.build();
+
+        count = countAPI.post(request);
+        Assert.assertEquals(4L, count);
+
+        builder = CountRequest.builder();
+        builder.source("A").containsTraversed(false);
+        builder.steps().direction(Direction.OUT)
+               .labels(ImmutableList.of("link"));
+        builder.steps().direction(Direction.OUT)
+               .labels(ImmutableList.of("link"));
+        builder.steps().direction(Direction.OUT)
+               .labels(ImmutableList.of("link"))
+               .properties("time", "2020-01-07");
+        request = builder.build();
+
+        count = countAPI.post(request);
+        Assert.assertEquals(1L, count);
+    }
+
+    @Test
+    public void testCountWithDegree() {
+        CountRequest.Builder builder = CountRequest.builder();
+        builder.source("A").containsTraversed(false);
+        builder.steps().direction(Direction.OUT);
+        builder.steps().direction(Direction.OUT);
+        builder.steps().direction(Direction.OUT);
+        CountRequest request = builder.build();
+
+        long count = countAPI.post(request);
+        Assert.assertEquals(8L, count);
+
+        builder = CountRequest.builder();
+        builder.source("A").containsTraversed(false);
+        builder.steps().direction(Direction.OUT).degree(1);
+        builder.steps().direction(Direction.OUT);
+        builder.steps().direction(Direction.OUT);
+        request = builder.build();
+
+        count = countAPI.post(request);
+        Assert.assertEquals(3L, count);
+
+        builder = CountRequest.builder();
+        builder.source("A").containsTraversed(false);
+        builder.steps().direction(Direction.OUT);
+        builder.steps().direction(Direction.OUT).degree(2);
+        builder.steps().direction(Direction.OUT);
+        request = builder.build();
+
+        count = countAPI.post(request);
+        Assert.assertEquals(4L, count);
+
+        builder = CountRequest.builder();
+        builder.source("A").containsTraversed(false);
+        builder.steps().direction(Direction.OUT);
+        builder.steps().direction(Direction.OUT).degree(4);
+        builder.steps().direction(Direction.OUT);
+        request = builder.build();
+
+        count = countAPI.post(request);
+        Assert.assertEquals(7L, count);
+    }
+
+    @Test
+    public void testCountWithSkipDegree() {
+        CountRequest.Builder builder = CountRequest.builder();
+        builder.source("A").containsTraversed(false);
+        builder.steps().direction(Direction.OUT);
+        builder.steps().direction(Direction.OUT);
+        builder.steps().direction(Direction.OUT);
+        CountRequest request = builder.build();
+
+        long count = countAPI.post(request);
+        Assert.assertEquals(8L, count);
+
+        builder = CountRequest.builder();
+        builder.source("A").containsTraversed(false);
+        builder.steps().direction(Direction.OUT);
+        builder.steps().direction(Direction.OUT).degree(3).skipDegree(5);
+        builder.steps().direction(Direction.OUT);
+        request = builder.build();
+
+        count = countAPI.post(request);
+        Assert.assertEquals(3L, count);
+
+        builder = CountRequest.builder();
+        builder.source("A").containsTraversed(false);
+        builder.steps().direction(Direction.OUT);
+        builder.steps().direction(Direction.OUT).degree(2).skipDegree(3);
+        builder.steps().direction(Direction.OUT);
+        request = builder.build();
+
+        count = countAPI.post(request);
+        Assert.assertEquals(0L, count);
+
+        builder = CountRequest.builder();
+        builder.source("A").containsTraversed(false);
+        builder.steps().direction(Direction.OUT);
+        builder.steps().direction(Direction.OUT).degree(3).skipDegree(4);
+        request = builder.build();
+
+        count = countAPI.post(request);
+        Assert.assertEquals(3L, count);
+    }
+
+    @Test
+    public void testCountWithIllegalArgument() {
+        CountRequest.Builder builder = CountRequest.builder();
+
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            builder.source(null);
+        }, e -> {
+            Assert.assertContains("The source can't be null", e.getMessage());
+        });
+
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            builder.dedupSize(-5);
+        }, e -> {
+            Assert.assertContains("The dedup size must be >= 0 or == -1, " +
+                                  "but got: ", e.getMessage());
+        });
+
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            builder.steps().degree(0);
+        }, e -> {
+            Assert.assertContains("Degree must be > 0 or == -1, but got: ",
+                                  e.getMessage());
+        });
+
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            builder.steps().skipDegree(-3);
+        }, e -> {
+            Assert.assertContains("The skipped degree must be >= 0, but got",
+                                  e.getMessage());
+        });
+
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            builder.steps().degree(5).skipDegree(3);
+        }, e -> {
+            Assert.assertContains("The skipped degree must be >= max degree",
+                                  e.getMessage());
+        });
+
+        CountRequest.Builder builder1 = CountRequest.builder();
+        Assert.assertThrows(ServerException.class, () -> {
+            builder1.source("A").containsTraversed(false);
+            builder1.steps().properties(ImmutableMap.of("weight", 3.3D));
+            countAPI.post(builder1.build());
+        }, e -> {
+            Assert.assertContains("The properties filter condition can be " +
+                                  "set only if just set one edge label",
+                                  e.getMessage());
+        });
+
+        CountRequest.Builder builder2 = CountRequest.builder();
+        Assert.assertThrows(ServerException.class, () -> {
+            builder2.source("A").containsTraversed(false);
+            builder2.steps().labels(ImmutableList.of("link", "relateTo"))
+                    .properties(ImmutableMap.of("weight", 3.3D));
+            countAPI.post(builder2.build());
+        }, e -> {
+            Assert.assertContains("The properties filter condition can be " +
+                                  "set only if just set one edge label",
+                                  e.getMessage());
+        });
+
+        CountRequest.Builder builder3 = CountRequest.builder();
+        builder3.source("A").containsTraversed(false);
+        builder3.steps().labels(ImmutableList.of("link"))
+                .properties(ImmutableMap.of("time", "2020-01-01"));
+        countAPI.post(builder3.build());
+
+        CountRequest.Builder builder4 = CountRequest.builder();
+        Assert.assertThrows(ServerException.class, () -> {
+            builder4.source("A").containsTraversed(false);
+            builder4.steps().labels(ImmutableList.of("link"))
+                    .properties(ImmutableMap.of("weight", 3.3D));
+            countAPI.post(builder4.build());
+        }, e -> {
+            Assert.assertContains("does not match sort keys of edge label",
+                                  e.getMessage());
+        });
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/api/traverser/CustomizedPathsApiTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/traverser/CustomizedPathsApiTest.java
new file mode 100644
index 0000000..755cbc4
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/traverser/CustomizedPathsApiTest.java
@@ -0,0 +1,374 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.traverser;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.baidu.hugegraph.api.BaseApiTest;
+import com.baidu.hugegraph.driver.SchemaManager;
+import com.baidu.hugegraph.exception.ServerException;
+import com.baidu.hugegraph.structure.constant.Direction;
+import com.baidu.hugegraph.structure.graph.Vertex;
+import com.baidu.hugegraph.structure.schema.EdgeLabel;
+import com.baidu.hugegraph.structure.traverser.PathsWithVertices;
+import com.baidu.hugegraph.structure.traverser.CustomizedPathsRequest;
+import com.baidu.hugegraph.testutil.Assert;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+
+public class CustomizedPathsApiTest extends TraverserApiTest {
+
+    @BeforeClass
+    public static void prepareSchemaAndGraph() {
+        BaseApiTest.initPropertyKey();
+        BaseApiTest.initVertexLabel();
+        BaseApiTest.initVertex();
+    }
+
+    @Override
+    @Before
+    public void setup() {
+        initEdgesWithWeights();
+    }
+
+    @Override
+    @After
+    public void teardown() {
+        removeEdgesWithWeights();
+    }
+
+    @Test
+    public void testCustomizedPathsSourceLabelProperty() {
+        Object markoId = getVertexId("person", "name", "marko");
+        Object joshId = getVertexId("person", "name", "josh");
+        Object rippleId = getVertexId("software", "name", "ripple");
+        Object lopId = getVertexId("software", "name", "lop");
+
+        CustomizedPathsRequest.Builder builder = CustomizedPathsRequest.builder();
+        builder.sources().label("person").property("name", "marko");
+        builder.steps().direction(Direction.OUT).labels("knows1")
+               .weightBy("weight").degree(-1);
+        builder.steps().direction(Direction.OUT).labels("created1")
+               .weightBy("weight").degree(-1);
+        builder.sortBy(CustomizedPathsRequest.SortBy.INCR).withVertex(true)
+               .capacity(-1).limit(-1);
+        CustomizedPathsRequest request = builder.build();
+
+        PathsWithVertices customizedPaths = customizedPathsAPI.post(request);
+        List<PathsWithVertices.Paths> paths = customizedPaths.paths();
+
+        Assert.assertEquals(2, paths.size());
+
+        List<Object> path1 = ImmutableList.of(markoId, joshId, lopId);
+        List<Object> path2 = ImmutableList.of(markoId, joshId, rippleId);
+        Assert.assertEquals(path1, paths.get(0).objects());
+        Assert.assertEquals(path2, paths.get(1).objects());
+
+        List<Double> weights1 = ImmutableList.of(1.0D, 0.4D);
+        List<Double> weights2 = ImmutableList.of(1.0D, 1.0D);
+        Assert.assertEquals(weights1, paths.get(0).weights());
+        Assert.assertEquals(weights2, paths.get(1).weights());
+
+        Set<?> vertices = customizedPaths.vertices().stream()
+                                         .map(Vertex::id)
+                                         .collect(Collectors.toSet());
+        Assert.assertEquals(4, vertices.size());
+        Set<?> expectedVertices = ImmutableSet.of(markoId, lopId,
+                                                  rippleId, joshId);
+        Assert.assertEquals(expectedVertices, vertices);
+    }
+
+    @Test
+    public void testCustomizedPathsSourceIds() {
+        Object markoId = getVertexId("person", "name", "marko");
+        Object lopId = getVertexId("software", "name", "lop");
+        Object peterId = getVertexId("person", "name", "peter");
+
+        CustomizedPathsRequest.Builder builder = CustomizedPathsRequest.builder();
+        builder.sources().ids(markoId, peterId);
+        builder.steps().direction(Direction.OUT).labels("created1")
+               .weightBy("weight").degree(-1);
+        builder.sortBy(CustomizedPathsRequest.SortBy.INCR).withVertex(true)
+               .capacity(-1).limit(-1);
+        CustomizedPathsRequest request = builder.build();
+
+        PathsWithVertices customizedPaths = customizedPathsAPI.post(request);
+        List<PathsWithVertices.Paths> paths = customizedPaths.paths();
+
+        Assert.assertEquals(2, paths.size());
+
+        List<Object> path1 = ImmutableList.of(markoId, lopId);
+        List<Object> path2 = ImmutableList.of(peterId, lopId);
+        List<List<Object>> expectedPaths = ImmutableList.of(path1, path2);
+        Assert.assertTrue(expectedPaths.contains(paths.get(0).objects()));
+        Assert.assertTrue(expectedPaths.contains(paths.get(1).objects()));
+
+        List<Double> weights1 = ImmutableList.of(0.2D);
+        List<Double> weights2 = ImmutableList.of(0.4D);
+        List<List<Double>> expectedWeights = ImmutableList.of(weights1,
+                                                              weights2);
+        Assert.assertTrue(expectedWeights.contains(paths.get(0).weights()));
+        Assert.assertTrue(expectedWeights.contains(paths.get(1).weights()));
+
+        Set<?> vertices = customizedPaths.vertices().stream()
+                                         .map(Vertex::id)
+                                         .collect(Collectors.toSet());
+        Assert.assertEquals(3, vertices.size());
+        Set<?> expectedVertices = ImmutableSet.of(markoId, lopId, peterId);
+        Assert.assertEquals(expectedVertices, vertices);
+    }
+
+    @Test
+    public void testCustomizedPathsSourceLabelPropertyMultiValue() {
+        Object markoId = getVertexId("person", "name", "marko");
+        Object lopId = getVertexId("software", "name", "lop");
+        Object peterId = getVertexId("person", "name", "peter");
+
+        CustomizedPathsRequest.Builder builder = CustomizedPathsRequest.builder();
+        List<String> names = ImmutableList.of("marko", "peter");
+        builder.sources().label("person").property("name", names);
+        builder.steps().direction(Direction.OUT).labels("created1")
+               .weightBy("weight").degree(-1);
+        builder.sortBy(CustomizedPathsRequest.SortBy.INCR).withVertex(true)
+               .capacity(-1).limit(-1);
+        CustomizedPathsRequest request = builder.build();
+
+        PathsWithVertices customizedPaths = customizedPathsAPI.post(request);
+        List<PathsWithVertices.Paths> paths = customizedPaths.paths();
+
+        Assert.assertEquals(2, paths.size());
+
+        List<Object> path1 = ImmutableList.of(markoId, lopId);
+        List<Object> path2 = ImmutableList.of(peterId, lopId);
+        List<List<Object>> expectedPaths = ImmutableList.of(path1, path2);
+        Assert.assertTrue(expectedPaths.contains(paths.get(0).objects()));
+        Assert.assertTrue(expectedPaths.contains(paths.get(1).objects()));
+
+        List<Double> weights1 = ImmutableList.of(0.2D);
+        List<Double> weights2 = ImmutableList.of(0.4D);
+        List<List<Double>> expectedWeights = ImmutableList.of(weights1,
+                                                              weights2);
+        Assert.assertTrue(expectedWeights.contains(paths.get(0).weights()));
+        Assert.assertTrue(expectedWeights.contains(paths.get(1).weights()));
+
+        Set<?> vertices = customizedPaths.vertices().stream()
+                                         .map(Vertex::id)
+                                         .collect(Collectors.toSet());
+        Assert.assertEquals(3, vertices.size());
+        Set<?> expectedVertices = ImmutableSet.of(markoId, lopId, peterId);
+        Assert.assertEquals(expectedVertices, vertices);
+    }
+
+    @Test
+    public void testCustomizedPathsWithSample() {
+        Object markoId = getVertexId("person", "name", "marko");
+        Object joshId = getVertexId("person", "name", "josh");
+        Object rippleId = getVertexId("software", "name", "ripple");
+        Object lopId = getVertexId("software", "name", "lop");
+
+        CustomizedPathsRequest.Builder builder = CustomizedPathsRequest.builder();
+        builder.sources().label("person").property("name", "marko");
+        builder.steps().direction(Direction.OUT).labels("knows1")
+               .weightBy("weight").degree(-1);
+        builder.steps().direction(Direction.OUT).labels("created1")
+               .weightBy("weight").degree(-1).sample(1);
+        builder.sortBy(CustomizedPathsRequest.SortBy.INCR).withVertex(true)
+               .capacity(-1).limit(-1);
+        CustomizedPathsRequest request = builder.build();
+
+        PathsWithVertices customizedPaths = customizedPathsAPI.post(request);
+        List<PathsWithVertices.Paths> paths = customizedPaths.paths();
+
+        Assert.assertEquals(1, paths.size());
+
+        List<Object> path1 = ImmutableList.of(markoId, joshId, rippleId);
+        List<Object> path2 = ImmutableList.of(markoId, joshId, lopId);
+        List<List<Object>> expectedPaths = ImmutableList.of(path1, path2);
+        Assert.assertTrue(expectedPaths.contains(paths.get(0).objects()));
+
+        List<Double> weights1 = ImmutableList.of(1D, 0.4D);
+        List<Double> weights2 = ImmutableList.of(1D, 1D);
+
+        Assert.assertTrue(weights1.equals(paths.get(0).weights()) ||
+                          weights2.equals(paths.get(0).weights()));
+
+        Set<?> vertices = customizedPaths.vertices().stream()
+                                         .map(Vertex::id)
+                                         .collect(Collectors.toSet());
+        Assert.assertEquals(3, vertices.size());
+        Assert.assertTrue(path1.containsAll(vertices) ||
+                          path2.containsAll(vertices));
+    }
+
+    @Test
+    public void testCustomizedPathsWithDecr() {
+        Object markoId = getVertexId("person", "name", "marko");
+        Object joshId = getVertexId("person", "name", "josh");
+        Object rippleId = getVertexId("software", "name", "ripple");
+        Object lopId = getVertexId("software", "name", "lop");
+
+        CustomizedPathsRequest.Builder builder = CustomizedPathsRequest.builder();
+        builder.sources().label("person").property("name", "marko");
+        builder.steps().direction(Direction.OUT).labels("knows1")
+               .weightBy("weight").degree(-1);
+        builder.steps().direction(Direction.OUT).labels("created1")
+               .weightBy("weight").degree(-1);
+        builder.sortBy(CustomizedPathsRequest.SortBy.DECR).withVertex(true)
+               .capacity(-1).limit(-1);
+        CustomizedPathsRequest request = builder.build();
+
+        PathsWithVertices customizedPaths = customizedPathsAPI.post(request);
+        List<PathsWithVertices.Paths> paths = customizedPaths.paths();
+
+        Assert.assertEquals(2, paths.size());
+
+        List<Object> path1 = ImmutableList.of(markoId, joshId, rippleId);
+        List<Object> path2 = ImmutableList.of(markoId, joshId, lopId);
+        Assert.assertEquals(path1, paths.get(0).objects());
+        Assert.assertEquals(path2, paths.get(1).objects());
+
+        List<Double> weights1 = ImmutableList.of(1.0D, 1.0D);
+        List<Double> weights2 = ImmutableList.of(1.0D, 0.4D);
+        Assert.assertEquals(weights1, paths.get(0).weights());
+        Assert.assertEquals(weights2, paths.get(1).weights());
+
+        Set<?> vertices = customizedPaths.vertices().stream()
+                                         .map(Vertex::id)
+                                         .collect(Collectors.toSet());
+        Assert.assertEquals(4, vertices.size());
+        Set<?> expectedVertices = ImmutableSet.of(markoId, lopId,
+                                                  rippleId, joshId);
+        Assert.assertEquals(expectedVertices, vertices);
+    }
+
+    @Test
+    public void testCustomizedPathsWithLimit() {
+        Object markoId = getVertexId("person", "name", "marko");
+        Object joshId = getVertexId("person", "name", "josh");
+        Object rippleId = getVertexId("software", "name", "ripple");
+        Object lopId = getVertexId("software", "name", "lop");
+
+        CustomizedPathsRequest.Builder builder = CustomizedPathsRequest.builder();
+        builder.sources().label("person").property("name", "marko");
+        builder.steps().direction(Direction.OUT).labels("knows1")
+               .weightBy("weight").degree(-1);
+        builder.steps().direction(Direction.OUT).labels("created1")
+               .weightBy("weight").degree(-1);
+        builder.sortBy(CustomizedPathsRequest.SortBy.INCR).withVertex(true)
+               .capacity(-1).limit(1);
+        CustomizedPathsRequest request = builder.build();
+
+        PathsWithVertices customizedPaths = customizedPathsAPI.post(request);
+        List<PathsWithVertices.Paths> paths = customizedPaths.paths();
+
+        Assert.assertEquals(1, paths.size());
+
+        List<Object> path1 = ImmutableList.of(markoId, joshId, lopId);
+        List<Object> path2 = ImmutableList.of(markoId, joshId, rippleId);
+        List<List<Object>> expectedPaths = ImmutableList.of(path1, path2);
+        Assert.assertTrue(expectedPaths.contains(paths.get(0).objects()));
+
+        List<Double> weights1 = ImmutableList.of(1.0D, 0.4D);
+        List<Double> weights2 = ImmutableList.of(1.0D, 1.0D);
+        List<List<Double>> expectedWeights = ImmutableList.of(weights1,
+                                                              weights2);
+        Assert.assertTrue(expectedWeights.contains(paths.get(0).weights()));
+
+        Set<?> vertices = customizedPaths.vertices().stream()
+                                         .map(Vertex::id)
+                                         .collect(Collectors.toSet());
+        Assert.assertEquals(3, vertices.size());
+        Set<?> vertices1 = ImmutableSet.of(markoId, lopId, joshId);
+        Set<?> vertices2 = ImmutableSet.of(markoId, lopId, rippleId);
+        Set<Set<?>> expectedVertices = ImmutableSet.of(vertices1, vertices2);
+        Assert.assertTrue(expectedVertices.contains(vertices));
+    }
+
+    @Test
+    public void testCustomizedPathsWithCapacity() {
+        CustomizedPathsRequest.Builder builder = CustomizedPathsRequest.builder();
+        builder.sources().label("person").property("name", "marko");
+        builder.steps().direction(Direction.OUT).labels("knows1")
+               .weightBy("weight").degree(-1);
+        builder.steps().direction(Direction.OUT).labels("created1")
+               .weightBy("weight").degree(-1);
+        builder.sortBy(CustomizedPathsRequest.SortBy.INCR).withVertex(true)
+               .capacity(1).limit(-1);
+        CustomizedPathsRequest request = builder.build();
+
+        Assert.assertThrows(ServerException.class, () -> {
+            customizedPathsAPI.post(request);
+        }, e -> {
+            String expect = "Exceed capacity '1' while finding customized paths";
+            Assert.assertContains(expect, e.getMessage());
+        });
+    }
+
+    private static void initEdgesWithWeights() {
+        SchemaManager schema = schema();
+        schema.edgeLabel("knows1")
+              .sourceLabel("person")
+              .targetLabel("person")
+              .properties("date", "weight")
+              .nullableKeys("weight")
+              .ifNotExist()
+              .create();
+
+        schema.edgeLabel("created1")
+              .sourceLabel("person")
+              .targetLabel("software")
+              .properties("date", "weight")
+              .nullableKeys("weight")
+              .ifNotExist()
+              .create();
+
+        Vertex marko = getVertex("person", "name", "marko");
+        Vertex vadas = getVertex("person", "name", "vadas");
+        Vertex lop = getVertex("software", "name", "lop");
+        Vertex josh = getVertex("person", "name", "josh");
+        Vertex ripple = getVertex("software", "name", "ripple");
+        Vertex peter = getVertex("person", "name", "peter");
+
+        marko.addEdge("knows1", vadas, "date", "2016-01-10", "weight", 0.5);
+        marko.addEdge("knows1", josh, "date", "2013-02-20", "weight", 1.0);
+        marko.addEdge("created1", lop, "date", "2017-12-10", "weight", 0.4);
+        josh.addEdge("created1", lop, "date", "2009-11-11", "weight", 0.4);
+        josh.addEdge("created1", ripple, "date", "2017-12-10", "weight", 1.0);
+        peter.addEdge("created1", lop, "date", "2017-03-24", "weight", 0.2);
+    }
+
+    private static void removeEdgesWithWeights() {
+        List<Long> elTaskIds = new ArrayList<>();
+        EdgeLabel knows1 = schema().getEdgeLabel("knows1");
+        EdgeLabel created1 = schema().getEdgeLabel("created1");
+        ImmutableList.of(knows1, created1).forEach(edgeLabel -> {
+            elTaskIds.add(edgeLabelAPI.delete(edgeLabel.name()));
+        });
+        elTaskIds.forEach(BaseApiTest::waitUntilTaskCompleted);
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/api/traverser/FusiformSimilarityApiTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/traverser/FusiformSimilarityApiTest.java
new file mode 100644
index 0000000..a14b34f
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/traverser/FusiformSimilarityApiTest.java
@@ -0,0 +1,665 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.traverser;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.baidu.hugegraph.exception.ServerException;
+import com.baidu.hugegraph.structure.constant.Direction;
+import com.baidu.hugegraph.structure.constant.T;
+import com.baidu.hugegraph.structure.graph.Vertex;
+import com.baidu.hugegraph.structure.traverser.FusiformSimilarity;
+import com.baidu.hugegraph.structure.traverser.FusiformSimilarity.Similar;
+import com.baidu.hugegraph.structure.traverser.FusiformSimilarityRequest;
+import com.baidu.hugegraph.testutil.Assert;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+public class FusiformSimilarityApiTest extends TraverserApiTest {
+
+    @BeforeClass
+    public static void prepareSchemaAndGraph() {
+        schema().propertyKey("name").asText().ifNotExist().create();
+        schema().propertyKey("city").asText().ifNotExist().create();
+        schema().propertyKey("time").asDate().ifNotExist().create();
+
+        schema().vertexLabel("person")
+                .properties("name", "city")
+                .primaryKeys("name")
+                .ifNotExist()
+                .create();
+        schema().vertexLabel("book")
+                .properties("name")
+                .primaryKeys("name")
+                .ifNotExist()
+                .create();
+
+        schema().edgeLabel("read")
+                .sourceLabel("person").targetLabel("book")
+                .ifNotExist()
+                .create();
+        schema().edgeLabel("write")
+                .sourceLabel("person").targetLabel("book")
+                .properties("time")
+                .multiTimes()
+                .sortKeys("time")
+                .ifNotExist()
+                .create();
+
+        Vertex p1 = graph().addVertex(T.label, "person", "name", "p1",
+                                      "city", "Beijing");
+        Vertex p2 = graph().addVertex(T.label, "person", "name", "p2",
+                                      "city", "Shanghai");
+        Vertex p3 = graph().addVertex(T.label, "person", "name", "p3",
+                                      "city", "Beijing");
+
+        Vertex b1 = graph().addVertex(T.label, "book", "name", "b1");
+        Vertex b2 = graph().addVertex(T.label, "book", "name", "b2");
+        Vertex b3 = graph().addVertex(T.label, "book", "name", "b3");
+        Vertex b4 = graph().addVertex(T.label, "book", "name", "b4");
+        Vertex b5 = graph().addVertex(T.label, "book", "name", "b5");
+        Vertex b6 = graph().addVertex(T.label, "book", "name", "b6");
+        Vertex b7 = graph().addVertex(T.label, "book", "name", "b7");
+        Vertex b8 = graph().addVertex(T.label, "book", "name", "b8");
+        Vertex b9 = graph().addVertex(T.label, "book", "name", "b9");
+        Vertex b10 = graph().addVertex(T.label, "book", "name", "b10");
+
+        // p1 read b1-b9 (9 books)
+        p1.addEdge("read", b1);
+        p1.addEdge("read", b2);
+        p1.addEdge("read", b3);
+        p1.addEdge("read", b4);
+        p1.addEdge("read", b5);
+        p1.addEdge("read", b6);
+        p1.addEdge("read", b7);
+        p1.addEdge("read", b8);
+        p1.addEdge("read", b9);
+        // p2 read b2-b10 (9 books)
+        p2.addEdge("read", b2);
+        p2.addEdge("read", b3);
+        p2.addEdge("read", b4);
+        p2.addEdge("read", b5);
+        p2.addEdge("read", b6);
+        p2.addEdge("read", b7);
+        p2.addEdge("read", b8);
+        p2.addEdge("read", b9);
+        p2.addEdge("read", b10);
+        // p3 read b3-b9 (7 books)
+        p3.addEdge("read", b3);
+        p3.addEdge("read", b4);
+        p3.addEdge("read", b5);
+        p3.addEdge("read", b6);
+        p3.addEdge("read", b7);
+        p3.addEdge("read", b8);
+        p3.addEdge("read", b9);
+
+        p1.addEdge("write", b1, "time", "2019-11-13 00:00:00");
+        p1.addEdge("write", b1, "time", "2019-11-13 00:01:00");
+        p1.addEdge("write", b1, "time", "2019-11-13 00:02:00");
+
+        p1.addEdge("write", b2, "time", "2019-11-13 00:00:00");
+        p1.addEdge("write", b2, "time", "2019-11-13 00:01:00");
+
+        p1.addEdge("write", b3, "time", "2019-11-13 00:00:00");
+        p1.addEdge("write", b3, "time", "2019-11-13 00:01:00");
+
+        p2.addEdge("write", b1, "time", "2019-11-13 00:00:00");
+        p2.addEdge("write", b1, "time", "2019-11-13 00:01:00");
+        p2.addEdge("write", b1, "time", "2019-11-13 00:02:00");
+
+        p3.addEdge("write", b2, "time", "2019-11-13 00:00:00");
+        p3.addEdge("write", b2, "time", "2019-11-13 00:01:00");
+
+        p3.addEdge("write", b3, "time", "2019-11-13 00:00:00");
+        p3.addEdge("write", b3, "time", "2019-11-13 00:01:00");
+        p3.addEdge("write", b3, "time", "2019-11-13 00:02:00");
+    }
+
+    @Test
+    public void testFusiformSimilarity() {
+        Object p1 = getVertexId("person", "name", "p1");
+        Object p2 = getVertexId("person", "name", "p2");
+        Object p3 = getVertexId("person", "name", "p3");
+
+        FusiformSimilarityRequest.Builder builder =
+                FusiformSimilarityRequest.builder();
+        builder.sources().label("person").property("name", "p1");
+        builder.label("read").direction(Direction.OUT).minNeighbors(8)
+               .alpha(0.75D).groupProperty("city").minGroups(2);
+        builder.capacity(-1).limit(-1);
+        FusiformSimilarityRequest request = builder.build();
+
+        FusiformSimilarity results = fusiformSimilarityAPI.post(request);
+        Assert.assertEquals(1, results.size());
+        Map.Entry<Object, Set<Similar>> entry = results.first();
+        Assert.assertEquals(p1, entry.getKey());
+        Assert.assertEquals(2, entry.getValue().size());
+        Set<Object> expected = ImmutableSet.of(p2, p3);
+        Set<Object> actual = entry.getValue().stream().map(Similar::id)
+                                  .collect(Collectors.toSet());
+        Assert.assertEquals(expected, actual);
+    }
+
+    @Test
+    public void testFusiformSimilarityLessThanMinEdgeCount() {
+        FusiformSimilarityRequest.Builder builder =
+                FusiformSimilarityRequest.builder();
+        builder.sources().label("person").property("name", "p1");
+        builder.label("read").direction(Direction.OUT).minNeighbors(10)
+               .alpha(0.8D).groupProperty("city").minGroups(2);
+        builder.capacity(-1).limit(-1);
+        FusiformSimilarityRequest request = builder.build();
+        FusiformSimilarity results = fusiformSimilarityAPI.post(request);
+        Assert.assertEquals(0, results.size());
+
+        builder = FusiformSimilarityRequest.builder();
+        builder.sources().label("person").property("name", "p1");
+        builder.label("read").direction(Direction.OUT).minNeighbors(9)
+               .alpha(0.8D).groupProperty("city").minGroups(2);
+        builder.capacity(-1).limit(-1);
+        request = builder.build();
+        results = fusiformSimilarityAPI.post(request);
+        Assert.assertEquals(1, results.size());
+
+        builder = FusiformSimilarityRequest.builder();
+        builder.sources().label("person").property("name", "p2");
+        builder.label("read").direction(Direction.OUT).minNeighbors(10)
+               .alpha(0.8D).groupProperty("city").minGroups(2);
+        builder.capacity(-1).limit(-1);
+        request = builder.build();
+        results = fusiformSimilarityAPI.post(request);
+        Assert.assertEquals(0, results.size());
+
+        builder = FusiformSimilarityRequest.builder();
+        builder.sources().label("person").property("name", "p2");
+        builder.label("read").direction(Direction.OUT).minNeighbors(9)
+               .alpha(0.8D).groupProperty("city").minGroups(2);
+        builder.capacity(-1).limit(-1);
+        request = builder.build();
+        results = fusiformSimilarityAPI.post(request);
+        Assert.assertEquals(1, results.size());
+
+        builder = FusiformSimilarityRequest.builder();
+        builder.sources().label("person").property("name", "p3");
+        builder.label("read").direction(Direction.OUT).minNeighbors(8)
+               .alpha(0.8D).groupProperty("city").minGroups(2);
+        builder.capacity(-1).limit(-1);
+        request = builder.build();
+        results = fusiformSimilarityAPI.post(request);
+        Assert.assertEquals(0, results.size());
+
+        builder = FusiformSimilarityRequest.builder();
+        builder.sources().label("person").property("name", "p2");
+        builder.label("read").direction(Direction.OUT).minNeighbors(7)
+               .alpha(0.8D).groupProperty("city").minGroups(2);
+        builder.capacity(-1).limit(-1);
+        request = builder.build();
+        results = fusiformSimilarityAPI.post(request);
+        Assert.assertEquals(1, results.size());
+    }
+
+    @Test
+    public void testFusiformSimilarityAlpha() {
+        Object p1 = getVertexId("person", "name", "p1");
+        Object p2 = getVertexId("person", "name", "p2");
+        Object p3 = getVertexId("person", "name", "p3");
+
+        FusiformSimilarityRequest.Builder builder =
+                FusiformSimilarityRequest.builder();
+        builder.sources().label("person").property("name", "p1");
+        builder.label("read").direction(Direction.OUT).minNeighbors(8)
+               .alpha(0.75D).groupProperty("city").minGroups(2);
+        builder.capacity(-1).limit(-1);
+        FusiformSimilarityRequest request = builder.build();
+
+        FusiformSimilarity results = fusiformSimilarityAPI.post(request);
+        Assert.assertEquals(1, results.size());
+        Map.Entry<Object, Set<Similar>> entry = results.first();
+        Assert.assertEquals(p1, entry.getKey());
+        Assert.assertEquals(2, entry.getValue().size());
+        Set<Object> expected = ImmutableSet.of(p2, p3);
+        Set<Object> actual = entry.getValue().stream().map(Similar::id)
+                                  .collect(Collectors.toSet());
+        Assert.assertEquals(expected, actual);
+
+        builder = FusiformSimilarityRequest.builder();
+        builder.sources().label("person").property("name", "p1");
+        builder.label("read").direction(Direction.OUT).minNeighbors(6)
+               .alpha(0.83D).groupProperty("city").minGroups(2);
+        builder.capacity(-1).limit(-1);
+        request = builder.build();
+        results = fusiformSimilarityAPI.post(request);
+        Assert.assertEquals(1, results.size());
+
+        entry = results.first();
+        Assert.assertEquals(p1, entry.getKey());
+        Assert.assertEquals(1, entry.getValue().size());
+        expected = ImmutableSet.of(p2);
+        actual = entry.getValue().stream().map(Similar::id)
+                      .collect(Collectors.toSet());
+        Assert.assertEquals(expected, actual);
+    }
+
+    @Test
+    public void testFusiformSimilarityMinSimilars() {
+        Object p1 = getVertexId("person", "name", "p1");
+        Object p2 = getVertexId("person", "name", "p2");
+        Object p3 = getVertexId("person", "name", "p3");
+
+        FusiformSimilarityRequest.Builder builder =
+                FusiformSimilarityRequest.builder();
+        builder.sources().label("person").property("name", "p1");
+        builder.label("read").direction(Direction.OUT).minNeighbors(8)
+               .alpha(0.75D).minSimilars(3).groupProperty("city")
+               .minGroups(2);
+        builder.capacity(-1).limit(-1);
+        FusiformSimilarityRequest request = builder.build();
+
+        FusiformSimilarity results = fusiformSimilarityAPI.post(request);
+        Assert.assertEquals(0, results.size());
+
+        builder = FusiformSimilarityRequest.builder();
+        builder.sources().label("person").property("name", "p1");
+        builder.label("read").direction(Direction.OUT).minNeighbors(8)
+               .alpha(0.75D).minSimilars(2).groupProperty("city")
+               .minGroups(2);
+        builder.capacity(-1).limit(-1);
+        request = builder.build();
+
+        results = fusiformSimilarityAPI.post(request);
+        Assert.assertEquals(1, results.size());
+        Map.Entry<Object, Set<Similar>> entry = results.first();
+        Assert.assertEquals(p1, entry.getKey());
+        Assert.assertEquals(2, entry.getValue().size());
+        Set<Object> expected = ImmutableSet.of(p2, p3);
+        Set<Object> actual = entry.getValue().stream().map(Similar::id)
+                                  .collect(Collectors.toSet());
+        Assert.assertEquals(expected, actual);
+
+        builder = FusiformSimilarityRequest.builder();
+        builder.sources().label("person").property("name", "p1");
+        builder.label("read").direction(Direction.OUT).minNeighbors(8)
+               .alpha(0.75D).minSimilars(1).groupProperty("city")
+               .minGroups(2);
+        builder.capacity(-1).limit(-1);
+        request = builder.build();
+
+        results = fusiformSimilarityAPI.post(request);
+        Assert.assertEquals(1, results.size());
+        entry = results.first();
+        Assert.assertEquals(p1, entry.getKey());
+        Assert.assertEquals(2, entry.getValue().size());
+        Assert.assertEquals(expected, actual);
+
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            FusiformSimilarityRequest.builder().minSimilars(0);
+        });
+    }
+
+    @Test
+    public void testFusiformSimilarityTop() {
+        Object p1 = getVertexId("person", "name", "p1");
+        Object p2 = getVertexId("person", "name", "p2");
+        Object p3 = getVertexId("person", "name", "p3");
+
+        FusiformSimilarityRequest.Builder builder =
+                FusiformSimilarityRequest.builder();
+        builder.sources().label("person").property("name", "p1");
+        builder.label("read").direction(Direction.OUT).minNeighbors(8)
+               .alpha(0.75D).top(2);
+        builder.capacity(-1).limit(-1);
+        FusiformSimilarityRequest request = builder.build();
+
+        FusiformSimilarity results = fusiformSimilarityAPI.post(request);
+        Assert.assertEquals(1, results.size());
+        Map.Entry<Object, Set<Similar>> entry = results.first();
+        Assert.assertEquals(p1, entry.getKey());
+        Assert.assertEquals(2, entry.getValue().size());
+        Set<Object> expected = ImmutableSet.of(p2, p3);
+        Set<Object> actual = entry.getValue().stream().map(Similar::id)
+                                  .collect(Collectors.toSet());
+        Assert.assertEquals(expected, actual);
+
+        builder = FusiformSimilarityRequest.builder();
+        builder.sources().label("person").property("name", "p1");
+        builder.label("read").direction(Direction.OUT).minNeighbors(6)
+               .alpha(0.8D).top(1);
+        builder.capacity(-1).limit(-1);
+        request = builder.build();
+        results = fusiformSimilarityAPI.post(request);
+        Assert.assertEquals(1, results.size());
+
+        entry = results.first();
+        Assert.assertEquals(p1, entry.getKey());
+        Assert.assertEquals(1, entry.getValue().size());
+        expected = ImmutableSet.of(p2);
+        actual = entry.getValue().stream().map(Similar::id)
+                      .collect(Collectors.toSet());
+        Assert.assertEquals(expected, actual);
+    }
+
+    @Test
+    public void testFusiformSimilarityMinGroupCount() {
+        Object p1 = getVertexId("person", "name", "p1");
+        Object p2 = getVertexId("person", "name", "p2");
+        Object p3 = getVertexId("person", "name", "p3");
+
+        FusiformSimilarityRequest.Builder builder =
+                FusiformSimilarityRequest.builder();
+        builder.sources().label("person").property("name", "p1");
+        builder.label("read").direction(Direction.OUT).minNeighbors(8)
+               .alpha(0.7D).groupProperty("city").minGroups(2);
+        builder.capacity(-1).limit(-1);
+        FusiformSimilarityRequest request = builder.build();
+
+        FusiformSimilarity results = fusiformSimilarityAPI.post(request);
+        Assert.assertEquals(1, results.size());
+        Map.Entry<Object, Set<Similar>> entry = results.first();
+        Assert.assertEquals(p1, entry.getKey());
+        Assert.assertEquals(2, entry.getValue().size());
+        Set<Object> expected = ImmutableSet.of(p2, p3);
+        Set<Object> actual = entry.getValue().stream().map(Similar::id)
+                                  .collect(Collectors.toSet());
+        Assert.assertEquals(expected, actual);
+
+        builder = FusiformSimilarityRequest.builder();
+        builder.sources().label("person").property("name", "p1");
+        builder.label("read").direction(Direction.OUT).minNeighbors(6)
+               .alpha(0.8D).groupProperty("city").minGroups(3);
+        builder.capacity(-1).limit(-1);
+        request = builder.build();
+        results = fusiformSimilarityAPI.post(request);
+        Assert.assertEquals(0, results.size());
+    }
+
+    @Test
+    public void testFusiformSimilarityCapacity() {
+        FusiformSimilarityRequest.Builder builder =
+                FusiformSimilarityRequest.builder();
+        builder.sources().label("person").property("name", "p1");
+        builder.label("read").direction(Direction.OUT).minNeighbors(8)
+               .alpha(0.8D).groupProperty("city").minGroups(2);
+        builder.capacity(10).limit(-1);
+        FusiformSimilarityRequest request = builder.build();
+
+        Assert.assertThrows(ServerException.class, () -> {
+            fusiformSimilarityAPI.post(request);
+        }, e -> {
+            String expect = "Exceed capacity '10' while " +
+                            "finding fusiform similarity";
+            Assert.assertContains(expect, e.getMessage());
+        });
+    }
+
+    @Test
+    public void testFusiformSimilarityLimit() {
+        Object p1 = getVertexId("person", "name", "p1");
+        Object p2 = getVertexId("person", "name", "p2");
+        Object p3 = getVertexId("person", "name", "p3");
+
+        FusiformSimilarityRequest.Builder builder =
+                FusiformSimilarityRequest.builder();
+        builder.sources().ids(p1, p2, p3);
+        builder.label("read").direction(Direction.OUT).minNeighbors(5)
+               .alpha(0.8D).top(2);
+        builder.capacity(-1).limit(-1);
+        FusiformSimilarityRequest request = builder.build();
+        FusiformSimilarity results = fusiformSimilarityAPI.post(request);
+        Assert.assertEquals(3, results.size());
+
+        builder = FusiformSimilarityRequest.builder();
+        builder.sources().ids(p1, p2, p3);
+        builder.label("read").direction(Direction.OUT).minNeighbors(5)
+               .alpha(0.8D).top(2);
+        builder.capacity(-1).limit(2);
+        request = builder.build();
+        results = fusiformSimilarityAPI.post(request);
+        Assert.assertEquals(2, results.size());
+
+        builder = FusiformSimilarityRequest.builder();
+        builder.sources().ids(p1, p2, p3);
+        builder.label("read").direction(Direction.OUT).minNeighbors(5)
+               .alpha(0.8D).top(2);
+        builder.capacity(-1).limit(1);
+        request = builder.build();
+        results = fusiformSimilarityAPI.post(request);
+        Assert.assertEquals(1, results.size());
+    }
+
+    @Test
+    public void testFusiformSimilarityWithMultiTimesEdges() {
+        Vertex p1 = getVertex("person", "name", "p1");
+        Vertex p2 = getVertex("person", "name", "p2");
+        Vertex p3 = getVertex("person", "name", "p3");
+
+        Object id1 = p1.id();
+        Object id2 = p2.id();
+        Object id3 = p3.id();
+
+        FusiformSimilarityRequest.Builder builder =
+                FusiformSimilarityRequest.builder();
+        builder.sources().ids(id1, id2, id3);
+        builder.label("write").direction(Direction.OUT).minNeighbors(3)
+               .alpha(0.666D);
+        builder.capacity(-1).limit(-1);
+        FusiformSimilarityRequest request = builder.build();
+        FusiformSimilarity results = fusiformSimilarityAPI.post(request);
+        Assert.assertEquals(1, results.size());
+        Map.Entry<Object, Set<Similar>> entry = results.first();
+        Assert.assertEquals(id1, entry.getKey());
+        Assert.assertEquals(1, entry.getValue().size());
+        Set<Object> actual = entry.getValue().stream().map(Similar::id)
+                                  .collect(Collectors.toSet());
+        Assert.assertEquals(ImmutableSet.of(id3), actual);
+    }
+
+    @Test
+    public void testFusiformSimilarityWithoutEdgeLabel() {
+        Object p1 = getVertexId("person", "name", "p1");
+        Object p2 = getVertexId("person", "name", "p2");
+        Object p3 = getVertexId("person", "name", "p3");
+
+        FusiformSimilarityRequest.Builder builder =
+                FusiformSimilarityRequest.builder();
+        builder.sources().ids(p1, p2, p3);
+        builder.direction(Direction.OUT).minNeighbors(8)
+               .alpha(0.875D);
+        builder.capacity(-1).limit(-1);
+        FusiformSimilarityRequest request = builder.build();
+
+        FusiformSimilarity results = fusiformSimilarityAPI.post(request);
+        Assert.assertEquals(3, results.size());
+        Map<Object, Set<Object>> expected = ImmutableMap.of(
+                p1, ImmutableSet.of(p2, p3),
+                p2, ImmutableSet.of(p1),
+                p3, ImmutableSet.of(p1, p2)
+        );
+
+        for (Map.Entry<Object, Set<Similar>> e : results.similarsMap()
+                                                        .entrySet()) {
+            Object key = e.getKey();
+            Set<Object> actual = e.getValue().stream().map(Similar::id)
+                                  .collect(Collectors.toSet());
+            Assert.assertEquals(expected.get(key), actual);
+        }
+    }
+
+    @Test
+    public void testFusiformSimilarityWithIntermediaryAndVertex() {
+        Vertex p1 = getVertex("person", "name", "p1");
+        Vertex p2 = getVertex("person", "name", "p2");
+        Vertex p3 = getVertex("person", "name", "p3");
+
+        Vertex b2 = getVertex("book", "name", "b2");
+        Vertex b3 = getVertex("book", "name", "b3");
+        Vertex b4 = getVertex("book", "name", "b4");
+        Vertex b5 = getVertex("book", "name", "b5");
+        Vertex b6 = getVertex("book", "name", "b6");
+        Vertex b7 = getVertex("book", "name", "b7");
+        Vertex b8 = getVertex("book", "name", "b8");
+        Vertex b9 = getVertex("book", "name", "b9");
+
+        Object p1Id = p1.id();
+        Object p2Id = p2.id();
+        Object p3Id = p3.id();
+
+        Object b2Id = b2.id();
+        Object b3Id = b3.id();
+        Object b4Id = b4.id();
+        Object b5Id = b5.id();
+        Object b6Id = b6.id();
+        Object b7Id = b7.id();
+        Object b8Id = b8.id();
+        Object b9Id = b9.id();
+
+        FusiformSimilarityRequest.Builder builder =
+                FusiformSimilarityRequest.builder();
+        builder.sources().label("person").property("name", "p1");
+        builder.label("read").direction(Direction.OUT).minNeighbors(8)
+               .alpha(0.75D).groupProperty("city").minGroups(2)
+               .withIntermediary(true).withVertex(true);
+        builder.capacity(-1).limit(-1);
+        FusiformSimilarityRequest request = builder.build();
+
+        FusiformSimilarity results = fusiformSimilarityAPI.post(request);
+        Assert.assertEquals(1, results.size());
+        Map.Entry<Object, Set<Similar>> entry = results.first();
+        Assert.assertEquals(p1Id, entry.getKey());
+
+        Assert.assertEquals(2, entry.getValue().size());
+        Set<Similar> similars = entry.getValue();
+        Set<Object> p2Inter = ImmutableSet.of(b2Id, b3Id, b4Id, b5Id,
+                                              b6Id, b7Id, b8Id, b9Id);
+        Set<Object> p3Inter = ImmutableSet.of(b3Id, b4Id, b5Id, b6Id,
+                                              b7Id, b8Id, b9Id);
+        for (Similar similar : similars) {
+            if (similar.id().equals(p2Id)) {
+                Assert.assertEquals(p2Inter, similar.intermediaries());
+            } else {
+                Assert.assertEquals(p3Id, similar.id());
+                Assert.assertEquals(p3Inter, similar.intermediaries());
+            }
+        }
+        Set<Vertex> vertices = ImmutableSet.of(p1, p2, p3, b2, b3, b4,
+                                               b5, b6, b7, b8, b9);
+        Assert.assertEquals(vertices, results.vertices());
+    }
+
+    @Test
+    public void testFusiformSimilarityWithIntermediaryWithoutVertex() {
+        Vertex p1 = getVertex("person", "name", "p1");
+        Vertex p2 = getVertex("person", "name", "p2");
+        Vertex p3 = getVertex("person", "name", "p3");
+
+        Vertex b2 = getVertex("book", "name", "b2");
+        Vertex b3 = getVertex("book", "name", "b3");
+        Vertex b4 = getVertex("book", "name", "b4");
+        Vertex b5 = getVertex("book", "name", "b5");
+        Vertex b6 = getVertex("book", "name", "b6");
+        Vertex b7 = getVertex("book", "name", "b7");
+        Vertex b8 = getVertex("book", "name", "b8");
+        Vertex b9 = getVertex("book", "name", "b9");
+
+        Object p1Id = p1.id();
+        Object p2Id = p2.id();
+        Object p3Id = p3.id();
+
+        Object b2Id = b2.id();
+        Object b3Id = b3.id();
+        Object b4Id = b4.id();
+        Object b5Id = b5.id();
+        Object b6Id = b6.id();
+        Object b7Id = b7.id();
+        Object b8Id = b8.id();
+        Object b9Id = b9.id();
+
+        FusiformSimilarityRequest.Builder builder =
+                FusiformSimilarityRequest.builder();
+        builder.sources().label("person").property("name", "p1");
+        builder.label("read").direction(Direction.OUT).minNeighbors(8)
+               .alpha(0.75D).groupProperty("city").minGroups(2)
+               .withIntermediary(true).withVertex(false);
+        builder.capacity(-1).limit(-1);
+        FusiformSimilarityRequest request = builder.build();
+
+        FusiformSimilarity results = fusiformSimilarityAPI.post(request);
+        Assert.assertEquals(1, results.size());
+        Map.Entry<Object, Set<Similar>> entry = results.first();
+        Assert.assertEquals(p1Id, entry.getKey());
+
+        Assert.assertEquals(2, entry.getValue().size());
+        Set<Similar> similars = entry.getValue();
+        Set<Object> p2Inter = ImmutableSet.of(b2Id, b3Id, b4Id, b5Id,
+                                              b6Id, b7Id, b8Id, b9Id);
+        Set<Object> p3Inter = ImmutableSet.of(b3Id, b4Id, b5Id, b6Id,
+                                              b7Id, b8Id, b9Id);
+        for (Similar similar : similars) {
+            if (similar.id().equals(p2Id)) {
+                Assert.assertEquals(p2Inter, similar.intermediaries());
+            } else {
+                Assert.assertEquals(p3Id, similar.id());
+                Assert.assertEquals(p3Inter, similar.intermediaries());
+            }
+        }
+        Set<Vertex> vertices = ImmutableSet.of();
+        Assert.assertEquals(vertices, results.vertices());
+    }
+
+    @Test
+    public void testFusiformSimilarityWithoutIntermediaryWithVertex() {
+        Vertex p1 = getVertex("person", "name", "p1");
+        Vertex p2 = getVertex("person", "name", "p2");
+        Vertex p3 = getVertex("person", "name", "p3");
+
+        FusiformSimilarityRequest.Builder builder =
+                FusiformSimilarityRequest.builder();
+        builder.sources().label("person").property("name", "p1");
+        builder.label("read").direction(Direction.OUT).minNeighbors(8)
+               .alpha(0.75D).groupProperty("city").minGroups(2)
+               .withIntermediary(false).withVertex(true);
+        builder.capacity(-1).limit(-1);
+        FusiformSimilarityRequest request = builder.build();
+
+        FusiformSimilarity results = fusiformSimilarityAPI.post(request);
+        Assert.assertEquals(1, results.size());
+        Map.Entry<Object, Set<Similar>> entry = results.first();
+        Assert.assertEquals(p1.id(), entry.getKey());
+
+        Assert.assertEquals(2, entry.getValue().size());
+        Set<Similar> similars = entry.getValue();
+        for (Similar similar : similars) {
+            if (similar.id().equals(p2.id())) {
+                Assert.assertEquals(ImmutableSet.of(),
+                                    similar.intermediaries());
+            } else {
+                Assert.assertEquals(p3.id(), similar.id());
+                Assert.assertEquals(ImmutableSet.of(),
+                                    similar.intermediaries());
+            }
+        }
+        Set<Vertex> vertices = ImmutableSet.of(p1, p2, p3);
+        Assert.assertEquals(vertices, results.vertices());
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/api/traverser/JaccardSimilarityApiTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/traverser/JaccardSimilarityApiTest.java
new file mode 100644
index 0000000..ad9e64c
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/traverser/JaccardSimilarityApiTest.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.traverser;
+
+import java.util.Map;
+import java.util.Set;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.baidu.hugegraph.structure.constant.Direction;
+import com.baidu.hugegraph.structure.constant.T;
+import com.baidu.hugegraph.structure.graph.Vertex;
+import com.baidu.hugegraph.structure.traverser.SingleSourceJaccardSimilarityRequest;
+
+
+import com.baidu.hugegraph.testutil.Assert;
+import com.google.common.collect.ImmutableSet;
+
+public class JaccardSimilarityApiTest extends TraverserApiTest {
+
+    @BeforeClass
+    public static void initShortestPathGraph() {
+        schema().vertexLabel("node")
+                .useCustomizeNumberId()
+                .ifNotExist()
+                .create();
+
+        schema().edgeLabel("link")
+                .sourceLabel("node").targetLabel("node")
+                .ifNotExist()
+                .create();
+        schema().edgeLabel("relateTo")
+                .sourceLabel("node").targetLabel("node")
+                .ifNotExist()
+                .create();
+
+        Vertex v1 = graph().addVertex(T.label, "node", T.id, 1);
+        Vertex v2 = graph().addVertex(T.label, "node", T.id, 2);
+        Vertex v3 = graph().addVertex(T.label, "node", T.id, 3);
+        Vertex v4 = graph().addVertex(T.label, "node", T.id, 4);
+        Vertex v5 = graph().addVertex(T.label, "node", T.id, 5);
+        Vertex v6 = graph().addVertex(T.label, "node", T.id, 6);
+        Vertex v7 = graph().addVertex(T.label, "node", T.id, 7);
+        Vertex v8 = graph().addVertex(T.label, "node", T.id, 8);
+        Vertex v9 = graph().addVertex(T.label, "node", T.id, 9);
+        Vertex v10 = graph().addVertex(T.label, "node", T.id, 10);
+
+        v1.addEdge("link", v3);
+        v2.addEdge("link", v3);
+        v4.addEdge("link", v1);
+        v4.addEdge("link", v2);
+
+        v1.addEdge("relateTo", v5);
+        v2.addEdge("relateTo", v5);
+        v6.addEdge("relateTo", v1);
+        v6.addEdge("relateTo", v2);
+
+        v1.addEdge("link", v7);
+        v8.addEdge("link", v1);
+        v2.addEdge("link", v9);
+        v10.addEdge("link", v2);
+    }
+
+    @Test
+    public void testJaccardSimilarity() {
+        double jaccard = jaccardSimilarityAPI.get(1, 2, Direction.BOTH,
+                                                  null, -1);
+        Assert.assertEquals(0.5D, jaccard, Double.MIN_VALUE);
+    }
+
+    @Test
+    public void testJaccardSimilarityWithDirection() {
+        double jaccard = jaccardSimilarityAPI.get(1, 2, Direction.OUT,
+                                                      null, -1);
+        Assert.assertEquals(0.5, jaccard, Double.MIN_VALUE);
+
+        jaccard = jaccardSimilarityAPI.get(1, 2, Direction.IN,
+                                         null, -1);
+        Assert.assertEquals(0.5, jaccard, Double.MIN_VALUE);
+    }
+
+    @Test
+    public void testJaccardSimilarityWithLabel() {
+        double jaccard = jaccardSimilarityAPI.get(1, 2, Direction.BOTH,
+                                                  "link", -1);
+        Assert.assertEquals(0.3333333333333333D, jaccard, Double.MIN_VALUE);
+
+        jaccard = jaccardSimilarityAPI.get(1, 2, Direction.OUT,
+                                           "link", -1);
+        Assert.assertEquals(0.3333333333333333D, jaccard, Double.MIN_VALUE);
+
+        jaccard = jaccardSimilarityAPI.get(1, 2, Direction.IN,
+                                         "link", -1);
+        Assert.assertEquals(0.3333333333333333D, jaccard, Double.MIN_VALUE);
+
+        jaccard = jaccardSimilarityAPI.get(1, 2, Direction.BOTH,
+                                         "relateTo", -1);
+        Assert.assertEquals(1.0D, jaccard, Double.MIN_VALUE);
+
+        jaccard = jaccardSimilarityAPI.get(1, 2, Direction.OUT,
+                                         "relateTo", -1);
+        Assert.assertEquals(1.0D, jaccard, Double.MIN_VALUE);
+
+        jaccard = jaccardSimilarityAPI.get(1, 2, Direction.IN,
+                                         "relateTo", -1);
+        Assert.assertEquals(1.0D, jaccard, Double.MIN_VALUE);
+    }
+
+    @Test
+    public void testJaccardSimilarityWithDegree() {
+        double jaccard = jaccardSimilarityAPI.get(1, 2, Direction.OUT,
+                                                  null, 6);
+        Assert.assertEquals(0.5D, jaccard, Double.MIN_VALUE);
+
+        jaccard = jaccardSimilarityAPI.get(1, 2, Direction.OUT,
+                                         null, 1);
+        Assert.assertEquals(1D, jaccard, Double.MIN_VALUE);
+    }
+
+    @Test
+    public void testJaccardSimilar() {
+        SingleSourceJaccardSimilarityRequest.Builder builder =
+                SingleSourceJaccardSimilarityRequest.builder();
+        builder.vertex(4);
+        builder.step().direction(Direction.BOTH);
+        SingleSourceJaccardSimilarityRequest request = builder.build();
+        Map<Object, Double> results = jaccardSimilarityAPI.post(request);
+
+        Assert.assertEquals(9, results.size());
+        Set<Object> expected = ImmutableSet.of("1", "2", "3", "5", "6",
+                                               "7", "8", "9", "10");
+        Assert.assertEquals(expected, results.keySet());
+
+        Assert.assertEquals(1.0, results.get("3"));
+        Assert.assertEquals(1.0, results.get("5"));
+        Assert.assertEquals(1.0, results.get("6"));
+        Assert.assertEquals(0.5, results.get("7"));
+        Assert.assertEquals(0.5, results.get("8"));
+        Assert.assertEquals(0.5, results.get("9"));
+        Assert.assertEquals(0.5, results.get("10"));
+        Assert.assertEquals(0.0, results.get("1"));
+        Assert.assertEquals(0.0, results.get("2"));
+    }
+
+    @Test
+    public void testJaccardSimilarWithTop() {
+        SingleSourceJaccardSimilarityRequest.Builder builder =
+                SingleSourceJaccardSimilarityRequest.builder();
+        builder.vertex(4);
+        builder.step().direction(Direction.BOTH);
+        builder.top(5);
+        SingleSourceJaccardSimilarityRequest request = builder.build();
+        Map<Object, Double> results = jaccardSimilarityAPI.post(request);
+
+        Assert.assertEquals(5, results.size());
+        Set<Object> expected = ImmutableSet.of("3", "5", "6", "7", "8");
+        Assert.assertEquals(expected, results.keySet());
+
+        Assert.assertEquals(1.0, results.get("3"));
+        Assert.assertEquals(1.0, results.get("5"));
+        Assert.assertEquals(1.0, results.get("6"));
+        Assert.assertEquals(0.5, results.get("7"));
+        Assert.assertEquals(0.5, results.get("8"));
+    }
+
+    @Test
+    public void testJaccardSimilarWithLabel() {
+        SingleSourceJaccardSimilarityRequest.Builder builder =
+                SingleSourceJaccardSimilarityRequest.builder();
+        builder.vertex(4);
+        builder.step().direction(Direction.BOTH).labels("link");
+        SingleSourceJaccardSimilarityRequest request = builder.build();
+        Map<Object, Double> results = jaccardSimilarityAPI.post(request);
+
+        Assert.assertEquals(7, results.size());
+        Set<Object> expected = ImmutableSet.of("3", "7", "8", "9",
+                                               "10", "1", "2");
+        Assert.assertEquals(expected, results.keySet());
+
+        Assert.assertEquals(1.0, results.get("3"));
+        Assert.assertEquals(0.5, results.get("7"));
+        Assert.assertEquals(0.5, results.get("8"));
+        Assert.assertEquals(0.5, results.get("9"));
+        Assert.assertEquals(0.5, results.get("10"));
+        Assert.assertEquals(0.0, results.get("1"));
+        Assert.assertEquals(0.0, results.get("2"));
+    }
+
+    @Test
+    public void testJaccardSimilarWithDirection() {
+        SingleSourceJaccardSimilarityRequest.Builder builder =
+                SingleSourceJaccardSimilarityRequest.builder();
+        builder.vertex(4);
+        builder.step().direction(Direction.OUT);
+        SingleSourceJaccardSimilarityRequest request = builder.build();
+        Map<Object, Double> results = jaccardSimilarityAPI.post(request);
+
+        Assert.assertEquals(6, results.size());
+        Set<Object> expected = ImmutableSet.of("1", "2", "3",
+                                               "5", "7", "9");
+        Assert.assertEquals(expected, results.keySet());
+
+        Assert.assertEquals(0.0, results.get("3"));
+        Assert.assertEquals(0.0, results.get("5"));
+        Assert.assertEquals(0.0, results.get("7"));
+        Assert.assertEquals(0.0, results.get("9"));
+        Assert.assertEquals(0.0, results.get("1"));
+        Assert.assertEquals(0.0, results.get("2"));
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/api/traverser/KneighborApiTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/traverser/KneighborApiTest.java
new file mode 100644
index 0000000..e87aeca
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/traverser/KneighborApiTest.java
@@ -0,0 +1,510 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.traverser;
+
+import java.util.List;
+import java.util.Set;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.baidu.hugegraph.api.BaseApiTest;
+import com.baidu.hugegraph.structure.constant.Direction;
+import com.baidu.hugegraph.structure.graph.Path;
+import com.baidu.hugegraph.structure.graph.Vertex;
+import com.baidu.hugegraph.structure.traverser.Kneighbor;
+import com.baidu.hugegraph.structure.traverser.KneighborRequest;
+import com.baidu.hugegraph.testutil.Assert;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+
+public class KneighborApiTest extends TraverserApiTest {
+
+    @BeforeClass
+    public static void prepareSchemaAndGraph() {
+        BaseApiTest.initPropertyKey();
+        BaseApiTest.initVertexLabel();
+        BaseApiTest.initEdgeLabel();
+        BaseApiTest.initIndexLabel();
+        BaseApiTest.initVertex();
+        BaseApiTest.initEdge();
+    }
+
+    @Test
+    public void testKneighborGet() {
+        Object markoId = getVertexId("person", "name", "marko");
+
+        long personId = vertexLabelAPI.get("person").id();
+        long softwareId = vertexLabelAPI.get("software").id();
+
+        List<Object> vertices = kneighborAPI.get(markoId, Direction.OUT,
+                                                 null, 2, -1L, -1L);
+        Assert.assertEquals(4, vertices.size());
+        Assert.assertTrue(vertices.contains(softwareId + ":lop"));
+        Assert.assertTrue(vertices.contains(softwareId + ":ripple"));
+        Assert.assertTrue(vertices.contains(personId + ":vadas"));
+        Assert.assertTrue(vertices.contains(personId + ":josh"));
+    }
+
+    @Test
+    public void testKneighborPost() {
+        Object markoId = getVertexId("person", "name", "marko");
+        Object rippleId = getVertexId("software", "name", "ripple");
+        Object joshId = getVertexId("person", "name", "josh");
+        Object lopId = getVertexId("software", "name", "lop");
+        Object vadasId = getVertexId("person", "name", "vadas");
+        Object peterId = getVertexId("person", "name", "peter");
+
+        KneighborRequest.Builder builder = KneighborRequest.builder();
+        builder.source(markoId);
+        builder.step().direction(Direction.BOTH);
+        builder.maxDepth(1);
+        KneighborRequest request = builder.build();
+
+        Kneighbor kneighborResult = kneighborAPI.post(request);
+
+        Assert.assertEquals(3, kneighborResult.size());
+        Set<Object> expected = ImmutableSet.of(vadasId, lopId, joshId);
+        Assert.assertEquals(expected, kneighborResult.ids());
+
+        builder = KneighborRequest.builder();
+        builder.source(markoId);
+        builder.step().direction(Direction.BOTH);
+        builder.maxDepth(2);
+        request = builder.build();
+
+        kneighborResult = kneighborAPI.post(request);
+
+        Assert.assertEquals(5, kneighborResult.size());
+        expected = ImmutableSet.of(vadasId, lopId, joshId, peterId, rippleId);
+        Assert.assertEquals(expected, kneighborResult.ids());
+    }
+
+    @Test
+    public void testKneighborPostWithPath() {
+        Object markoId = getVertexId("person", "name", "marko");
+        Object rippleId = getVertexId("software", "name", "ripple");
+        Object joshId = getVertexId("person", "name", "josh");
+        Object lopId = getVertexId("software", "name", "lop");
+        Object vadasId = getVertexId("person", "name", "vadas");
+        Object peterId = getVertexId("person", "name", "peter");
+
+        KneighborRequest.Builder builder = KneighborRequest.builder();
+        builder.source(markoId);
+        builder.step().direction(Direction.BOTH);
+        builder.maxDepth(1);
+        builder.withPath(true);
+        KneighborRequest request = builder.build();
+
+        Kneighbor kneighborResult = kneighborAPI.post(request);
+
+        Assert.assertEquals(3, kneighborResult.size());
+        Set<Object> expected = ImmutableSet.of(vadasId, lopId, joshId);
+        Assert.assertEquals(expected, kneighborResult.ids());
+        Assert.assertEquals(3, kneighborResult.paths().size());
+        List<Object> expectedPaths = ImmutableList.of(
+                ImmutableList.of(markoId, vadasId),
+                ImmutableList.of(markoId, lopId),
+                ImmutableList.of(markoId, joshId)
+        );
+        for (Path path : kneighborResult.paths()) {
+            Assert.assertTrue(expectedPaths.contains(path.objects()));
+        }
+
+        builder = KneighborRequest.builder();
+        builder.source(markoId);
+        builder.step().direction(Direction.BOTH);
+        builder.maxDepth(2);
+        builder.withPath(true);
+        request = builder.build();
+
+        kneighborResult = kneighborAPI.post(request);
+
+        Assert.assertEquals(5, kneighborResult.size());
+        expected = ImmutableSet.of(vadasId, peterId, joshId, lopId, rippleId);
+        Assert.assertEquals(expected, kneighborResult.ids());
+        Assert.assertEquals(5, kneighborResult.paths().size());
+        expectedPaths = ImmutableList.of(
+                ImmutableList.of(markoId, vadasId),
+                ImmutableList.of(markoId, lopId),
+                ImmutableList.of(markoId, joshId),
+                ImmutableList.of(markoId, lopId, peterId),
+                ImmutableList.of(markoId, joshId, rippleId)
+        );
+        for (Path path : kneighborResult.paths()) {
+            Assert.assertTrue(expectedPaths.contains(path.objects()));
+        }
+    }
+
+    @Test
+    public void testKneighborPostWithVertex() {
+        Object markoId = getVertexId("person", "name", "marko");
+        Object rippleId = getVertexId("software", "name", "ripple");
+        Object joshId = getVertexId("person", "name", "josh");
+        Object lopId = getVertexId("software", "name", "lop");
+        Object vadasId = getVertexId("person", "name", "vadas");
+        Object peterId = getVertexId("person", "name", "peter");
+
+        KneighborRequest.Builder builder = KneighborRequest.builder();
+        builder.source(markoId);
+        builder.step().direction(Direction.BOTH);
+        builder.maxDepth(1);
+        builder.withPath(false);
+        builder.withVertex(true);
+        KneighborRequest request = builder.build();
+
+        Kneighbor kneighborResult = kneighborAPI.post(request);
+
+        Assert.assertEquals(3, kneighborResult.size());
+        Set<Object> expected = ImmutableSet.of(vadasId, lopId, joshId);
+        Assert.assertEquals(expected, kneighborResult.ids());
+        Assert.assertEquals(3, kneighborResult.vertices().size());
+        Set<Object> expectedVids = ImmutableSet.of(vadasId, lopId, joshId);
+        for (Vertex vertex : kneighborResult.vertices()) {
+            Assert.assertTrue(expectedVids.contains(vertex.id()));
+        }
+
+        builder = KneighborRequest.builder();
+        builder.source(markoId);
+        builder.step().direction(Direction.BOTH);
+        builder.maxDepth(2);
+        builder.withPath(false);
+        builder.withVertex(true);
+        request = builder.build();
+
+        kneighborResult = kneighborAPI.post(request);
+
+        Assert.assertEquals(5, kneighborResult.size());
+        expected = ImmutableSet.of(vadasId, lopId, joshId, peterId, rippleId);
+        Assert.assertEquals(expected, kneighborResult.ids());
+        Assert.assertEquals(5, kneighborResult.vertices().size());
+        expectedVids = ImmutableSet.of(vadasId, lopId, joshId,
+                                       peterId, rippleId);
+        for (Vertex vertex : kneighborResult.vertices()) {
+            Assert.assertTrue(expectedVids.contains(vertex.id()));
+        }
+
+        builder = KneighborRequest.builder();
+        builder.source(markoId);
+        builder.step().direction(Direction.BOTH);
+        builder.maxDepth(1);
+        builder.withPath(true);
+        builder.withVertex(true);
+        request = builder.build();
+
+        kneighborResult = kneighborAPI.post(request);
+
+        Assert.assertEquals(3, kneighborResult.size());
+        expected = ImmutableSet.of(vadasId, lopId, joshId);
+        Assert.assertEquals(expected, kneighborResult.ids());
+        Assert.assertEquals(3, kneighborResult.paths().size());
+        Set<List<Object>> expectedPaths = ImmutableSet.of(
+                ImmutableList.of(markoId, vadasId),
+                ImmutableList.of(markoId, lopId),
+                ImmutableList.of(markoId, joshId)
+        );
+        for (Path path : kneighborResult.paths()) {
+            Assert.assertTrue(expectedPaths.contains(path.objects()));
+        }
+        Assert.assertEquals(4, kneighborResult.vertices().size());
+        expectedVids = ImmutableSet.of(markoId, vadasId, lopId, joshId);
+        for (Vertex vertex : kneighborResult.vertices()) {
+            Assert.assertTrue(expectedVids.contains(vertex.id()));
+        }
+
+        builder = KneighborRequest.builder();
+        builder.source(markoId);
+        builder.step().direction(Direction.BOTH);
+        builder.maxDepth(2);
+        builder.withPath(true);
+        builder.withVertex(true);
+        request = builder.build();
+
+        kneighborResult = kneighborAPI.post(request);
+
+        Assert.assertEquals(5, kneighborResult.size());
+        expected = ImmutableSet.of(peterId, lopId, joshId, rippleId, vadasId);
+        Assert.assertEquals(expected, kneighborResult.ids());
+        Assert.assertEquals(5, kneighborResult.paths().size());
+        expectedPaths = ImmutableSet.of(
+                ImmutableList.of(markoId, vadasId),
+                ImmutableList.of(markoId, lopId),
+                ImmutableList.of(markoId, joshId),
+                ImmutableList.of(markoId, lopId, peterId),
+                ImmutableList.of(markoId, joshId, rippleId)
+        );
+        for (Path path : kneighborResult.paths()) {
+            Assert.assertTrue(expectedPaths.contains(path.objects()));
+        }
+        Assert.assertEquals(6, kneighborResult.vertices().size());
+        expectedVids = ImmutableSet.of(markoId, peterId, lopId,
+                                       joshId, rippleId, vadasId);
+        for (Vertex vertex : kneighborResult.vertices()) {
+            Assert.assertTrue(expectedVids.contains(vertex.id()));
+        }
+    }
+
+    @Test
+    public void testKneighborPostWithLabel() {
+        Object markoId = getVertexId("person", "name", "marko");
+        Object joshId = getVertexId("person", "name", "josh");
+        Object lopId = getVertexId("software", "name", "lop");
+        Object vadasId = getVertexId("person", "name", "vadas");
+        Object peterId = getVertexId("person", "name", "peter");
+
+        KneighborRequest.Builder builder = KneighborRequest.builder();
+        builder.source(markoId);
+        builder.step().direction(Direction.BOTH).labels("created");
+        builder.maxDepth(1);
+        KneighborRequest request = builder.build();
+
+        Kneighbor kneighborResult = kneighborAPI.post(request);
+
+        Assert.assertEquals(1, kneighborResult.size());
+        Set<Object> expected = ImmutableSet.of(lopId);
+        Assert.assertEquals(expected, kneighborResult.ids());
+
+        builder = KneighborRequest.builder();
+        builder.source(markoId);
+        builder.step().direction(Direction.BOTH).labels("created");
+        builder.maxDepth(2);
+        request = builder.build();
+
+        kneighborResult = kneighborAPI.post(request);
+
+        Assert.assertEquals(3, kneighborResult.size());
+        expected = ImmutableSet.of(lopId, peterId, joshId);
+        Assert.assertEquals(expected, kneighborResult.ids());
+
+        builder = KneighborRequest.builder();
+        builder.source(markoId);
+        builder.step().direction(Direction.BOTH).labels("knows");
+        builder.maxDepth(1);
+        request = builder.build();
+
+        kneighborResult = kneighborAPI.post(request);
+
+        Assert.assertEquals(2, kneighborResult.size());
+        expected = ImmutableSet.of(vadasId, joshId);
+        Assert.assertEquals(expected, kneighborResult.ids());
+
+        builder = KneighborRequest.builder();
+        builder.source(markoId);
+        builder.step().direction(Direction.BOTH).labels("knows");
+        builder.maxDepth(2);
+        request = builder.build();
+
+        kneighborResult = kneighborAPI.post(request);
+
+        Assert.assertEquals(2, kneighborResult.size());
+        expected = ImmutableSet.of(vadasId, joshId);
+        Assert.assertEquals(expected, kneighborResult.ids());
+    }
+
+    @Test
+    public void testKneighborPostWithDirection() {
+        Object markoId = getVertexId("person", "name", "marko");
+        Object rippleId = getVertexId("software", "name", "ripple");
+        Object joshId = getVertexId("person", "name", "josh");
+        Object lopId = getVertexId("software", "name", "lop");
+        Object vadasId = getVertexId("person", "name", "vadas");
+
+        KneighborRequest.Builder builder = KneighborRequest.builder();
+        builder.source(markoId);
+        builder.step().direction(Direction.OUT);
+        builder.maxDepth(1);
+        KneighborRequest request = builder.build();
+
+        Kneighbor kneighborResult = kneighborAPI.post(request);
+
+        Assert.assertEquals(3, kneighborResult.size());
+        Set<Object> expected = ImmutableSet.of(vadasId, lopId, joshId);
+        Assert.assertEquals(expected, kneighborResult.ids());
+
+        builder = KneighborRequest.builder();
+        builder.source(markoId);
+        builder.step().direction(Direction.OUT);
+        builder.maxDepth(2);
+        request = builder.build();
+
+        kneighborResult = kneighborAPI.post(request);
+
+        Assert.assertEquals(4, kneighborResult.size());
+        expected = ImmutableSet.of(vadasId, lopId, joshId, rippleId);
+        Assert.assertEquals(expected, kneighborResult.ids());
+    }
+
+    @Test
+    public void testKneighborPostWithProperties() {
+        Object markoId = getVertexId("person", "name", "marko");
+        Object rippleId = getVertexId("software", "name", "ripple");
+        Object joshId = getVertexId("person", "name", "josh");
+        Object lopId = getVertexId("software", "name", "lop");
+        Object peterId = getVertexId("person", "name", "peter");
+
+        KneighborRequest.Builder builder = KneighborRequest.builder();
+        builder.source(markoId);
+        builder.step().direction(Direction.BOTH)
+               .properties("date", "P.gt(\"2014-01-01 00:00:00\")");
+        builder.maxDepth(1);
+        KneighborRequest request = builder.build();
+
+        Kneighbor kneighborResult = kneighborAPI.post(request);
+
+        Assert.assertEquals(1, kneighborResult.size());
+        Set<Object> expected = ImmutableSet.of(lopId);
+        Assert.assertEquals(expected, kneighborResult.ids());
+
+        builder = KneighborRequest.builder();
+        builder.source(markoId);
+        builder.step().direction(Direction.BOTH)
+               .properties("date", "P.gt(\"2014-01-01 00:00:00\")");
+        builder.maxDepth(2);
+        request = builder.build();
+
+        kneighborResult = kneighborAPI.post(request);
+
+        Assert.assertEquals(3, kneighborResult.size());
+        expected = ImmutableSet.of(lopId, peterId, joshId);
+        Assert.assertEquals(expected, kneighborResult.ids());
+
+        builder = KneighborRequest.builder();
+        builder.source(markoId);
+        builder.step().direction(Direction.BOTH)
+               .properties("date", "P.gt(\"2014-01-01 00:00:00\")");
+        builder.maxDepth(3);
+        request = builder.build();
+
+        kneighborResult = kneighborAPI.post(request);
+
+        Assert.assertEquals(4, kneighborResult.size());
+        expected = ImmutableSet.of(lopId, peterId, joshId, rippleId);
+        Assert.assertEquals(expected, kneighborResult.ids());
+    }
+
+    @Test
+    public void testKneighborPostWithLimit() {
+        Object markoId = getVertexId("person", "name", "marko");
+        Object rippleId = getVertexId("software", "name", "ripple");
+        Object joshId = getVertexId("person", "name", "josh");
+        Object lopId = getVertexId("software", "name", "lop");
+        Object vadasId = getVertexId("person", "name", "vadas");
+        Object peterId = getVertexId("person", "name", "peter");
+
+        // 1 depth with in&out edges
+        KneighborRequest.Builder builder = KneighborRequest.builder();
+        builder.source(markoId);
+        builder.step().direction(Direction.BOTH);
+        builder.maxDepth(1);
+        builder.limit(3);
+        KneighborRequest request = builder.build();
+
+        Kneighbor kneighborResult = kneighborAPI.post(request);
+
+        Assert.assertEquals(3, kneighborResult.size());
+        Set<Object> expected = ImmutableSet.of(vadasId, lopId, joshId);
+        Assert.assertTrue(expected.containsAll(kneighborResult.ids()));
+
+        // 2 depth with out edges
+        builder = KneighborRequest.builder();
+        builder.source(markoId);
+        builder.step().direction(Direction.OUT);
+        builder.maxDepth(2);
+        builder.limit(5);
+        request = builder.build();
+
+        kneighborResult = kneighborAPI.post(request);
+
+        Assert.assertEquals(4, kneighborResult.size());
+        expected = ImmutableSet.of(vadasId, lopId, joshId, rippleId);
+        Assert.assertTrue(expected.containsAll(kneighborResult.ids()));
+
+        // 2 depth with in&out edges
+        builder = KneighborRequest.builder();
+        builder.source(markoId);
+        builder.step().direction(Direction.BOTH);
+        builder.maxDepth(2);
+        builder.limit(5);
+        request = builder.build();
+
+        kneighborResult = kneighborAPI.post(request);
+
+        Assert.assertEquals(5, kneighborResult.size());
+        expected = ImmutableSet.of(vadasId, lopId, joshId, peterId, rippleId);
+        Assert.assertTrue(expected.containsAll(kneighborResult.ids()));
+    }
+
+    @Test
+    public void testKneighborPostWithCountOnly() {
+        Object markoId = getVertexId("person", "name", "marko");
+
+        KneighborRequest.Builder builder = KneighborRequest.builder();
+        builder.source(markoId);
+        builder.step().direction(Direction.BOTH);
+        builder.maxDepth(1);
+        builder.countOnly(true);
+        KneighborRequest request = builder.build();
+
+        Kneighbor kneighborResult = kneighborAPI.post(request);
+
+        Assert.assertEquals(3, kneighborResult.size());
+        Assert.assertTrue(kneighborResult.ids().isEmpty());
+        Assert.assertTrue(kneighborResult.paths().isEmpty());
+        Assert.assertTrue(kneighborResult.vertices().isEmpty());
+
+        builder = KneighborRequest.builder();
+        builder.source(markoId);
+        builder.step().direction(Direction.BOTH);
+        builder.maxDepth(2);
+        builder.countOnly(true);
+        request = builder.build();
+
+        kneighborResult = kneighborAPI.post(request);
+
+        Assert.assertEquals(5, kneighborResult.size());
+        Assert.assertTrue(kneighborResult.ids().isEmpty());
+        Assert.assertTrue(kneighborResult.paths().isEmpty());
+        Assert.assertTrue(kneighborResult.vertices().isEmpty());
+
+        builder = KneighborRequest.builder();
+        builder.source(markoId);
+        builder.step().direction(Direction.BOTH);
+        builder.maxDepth(1);
+        builder.countOnly(true);
+        builder.withPath(true);
+
+        KneighborRequest.Builder finalBuilder = builder;
+        Assert.assertThrows(IllegalArgumentException.class, ()-> {
+            finalBuilder.build();
+        });
+
+        builder = KneighborRequest.builder();
+        builder.source(markoId);
+        builder.step().direction(Direction.BOTH);
+        builder.maxDepth(1);
+        builder.countOnly(true);
+        builder.withVertex(true);
+
+        KneighborRequest.Builder finalBuilder1 = builder;
+        Assert.assertThrows(IllegalArgumentException.class, ()-> {
+            finalBuilder1.build();
+        });
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/api/traverser/KoutApiTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/traverser/KoutApiTest.java
new file mode 100644
index 0000000..3a8cef0
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/traverser/KoutApiTest.java
@@ -0,0 +1,625 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.traverser;
+
+import java.util.List;
+import java.util.Set;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.baidu.hugegraph.api.BaseApiTest;
+import com.baidu.hugegraph.exception.ServerException;
+import com.baidu.hugegraph.structure.constant.Direction;
+import com.baidu.hugegraph.structure.graph.Path;
+import com.baidu.hugegraph.structure.graph.Vertex;
+import com.baidu.hugegraph.structure.traverser.Kout;
+import com.baidu.hugegraph.structure.traverser.KoutRequest;
+import com.baidu.hugegraph.testutil.Assert;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+
+public class KoutApiTest extends TraverserApiTest {
+
+    @BeforeClass
+    public static void prepareSchemaAndGraph() {
+        BaseApiTest.initPropertyKey();
+        BaseApiTest.initVertexLabel();
+        BaseApiTest.initEdgeLabel();
+        BaseApiTest.initIndexLabel();
+        BaseApiTest.initVertex();
+        BaseApiTest.initEdge();
+    }
+
+    @Test
+    public void testKoutGetNearest() {
+        Object markoId = getVertexId("person", "name", "marko");
+
+        long softwareId = vertexLabelAPI.get("software").id();
+
+        List<Object> vertices = koutAPI.get(markoId, Direction.OUT,
+                                            null, 2, true, -1L, -1L, -1L);
+        Assert.assertEquals(1, vertices.size());
+        Assert.assertTrue(vertices.contains(softwareId + ":ripple"));
+    }
+
+    @Test
+    public void testKoutGetAll() {
+        Object markoId = getVertexId("person", "name", "marko");
+
+        long softwareId = vertexLabelAPI.get("software").id();
+
+        List<Object> vertices = koutAPI.get(markoId, Direction.OUT, null,
+                                            2, false, -1L, -1L, -1L);
+        Assert.assertEquals(2, vertices.size());
+        Assert.assertTrue(vertices.contains(softwareId + ":lop"));
+        Assert.assertTrue(vertices.contains(softwareId + ":ripple"));
+    }
+
+    @Test
+    public void testKoutGetBothNearest() {
+        Object markoId = getVertexId("person", "name", "marko");
+
+        long personId = vertexLabelAPI.get("person").id();
+        long softwareId = vertexLabelAPI.get("software").id();
+
+        List<Object> vertices = koutAPI.get(markoId, Direction.BOTH,
+                                            null, 2, true, -1L, -1L, -1L);
+        Assert.assertEquals(2, vertices.size());
+        Assert.assertTrue(vertices.contains(personId + ":peter"));
+        Assert.assertTrue(vertices.contains(softwareId + ":ripple"));
+    }
+
+    @Test
+    public void testKoutGetBothAll() {
+        Object markoId = getVertexId("person", "name", "marko");
+
+        long personId = vertexLabelAPI.get("person").id();
+        long softwareId = vertexLabelAPI.get("software").id();
+
+        List<Object> vertices = koutAPI.get(markoId, Direction.BOTH, null,
+                                            2, false, -1L, -1L, -1L);
+        Assert.assertEquals(4, vertices.size());
+        Assert.assertTrue(vertices.contains(personId + ":josh"));
+        Assert.assertTrue(vertices.contains(personId + ":peter"));
+        Assert.assertTrue(vertices.contains(softwareId + ":lop"));
+        Assert.assertTrue(vertices.contains(softwareId + ":ripple"));
+    }
+
+    @Test
+    public void testKoutGetBothAllWithCapacity() {
+        Object markoId = getVertexId("person", "name", "marko");
+
+        Assert.assertThrows(ServerException.class, () -> {
+            koutAPI.get(markoId, Direction.BOTH, null,
+                        2, false, -1L, 1L, 2L);
+        }, e -> {
+            String expect = "Capacity can't be less than limit, " +
+                            "but got capacity '1' and limit '2'";
+            Assert.assertContains(expect, e.getMessage());
+        });
+    }
+
+    @Test
+    public void testKoutGetBothAllWithCapacityNoLimit() {
+        Object markoId = getVertexId("person", "name", "marko");
+
+        Assert.assertThrows(ServerException.class, () -> {
+            koutAPI.get(markoId, Direction.BOTH, null,
+                    2, false, -1L, 1L, -1L);
+        }, e -> {
+            String expect = "Capacity can't be less than limit, " +
+                            "but got capacity '1' and limit '-1'";
+            Assert.assertContains(expect, e.getMessage());
+        });
+    }
+
+    @Test
+    public void testKoutPost() {
+        Object markoId = getVertexId("person", "name", "marko");
+        Object rippleId = getVertexId("software", "name", "ripple");
+        Object joshId = getVertexId("person", "name", "josh");
+        Object lopId = getVertexId("software", "name", "lop");
+        Object vadasId = getVertexId("person", "name", "vadas");
+        Object peterId = getVertexId("person", "name", "peter");
+
+        KoutRequest.Builder builder = KoutRequest.builder();
+        builder.source(markoId);
+        builder.step().direction(Direction.BOTH);
+        builder.maxDepth(1);
+        KoutRequest request = builder.build();
+
+        Kout koutResult = koutAPI.post(request);
+
+        Assert.assertEquals(3, koutResult.size());
+        Set<Object> expected = ImmutableSet.of(vadasId, lopId, joshId);
+        Assert.assertEquals(expected, koutResult.ids());
+
+        builder = KoutRequest.builder();
+        builder.source(markoId);
+        builder.step().direction(Direction.BOTH);
+        builder.maxDepth(2);
+        request = builder.build();
+
+        koutResult = koutAPI.post(request);
+
+        Assert.assertEquals(2, koutResult.size());
+        expected = ImmutableSet.of(peterId, rippleId);
+        Assert.assertEquals(expected, koutResult.ids());
+    }
+
+    @Test
+    public void testKoutPostWithNearest() {
+        Object markoId = getVertexId("person", "name", "marko");
+        Object rippleId = getVertexId("software", "name", "ripple");
+        Object joshId = getVertexId("person", "name", "josh");
+        Object lopId = getVertexId("software", "name", "lop");
+        Object vadasId = getVertexId("person", "name", "vadas");
+        Object peterId = getVertexId("person", "name", "peter");
+
+        KoutRequest.Builder builder = KoutRequest.builder();
+        builder.source(markoId);
+        builder.step().direction(Direction.BOTH);
+        builder.maxDepth(1);
+        builder.nearest(false);
+        KoutRequest request = builder.build();
+
+        Kout koutResult = koutAPI.post(request);
+
+        Assert.assertEquals(3, koutResult.size());
+        Set<Object> expected = ImmutableSet.of(vadasId, lopId, joshId);
+        Assert.assertEquals(expected, koutResult.ids());
+
+        builder = KoutRequest.builder();
+        builder.source(markoId);
+        builder.step().direction(Direction.BOTH);
+        builder.maxDepth(2);
+        builder.nearest(false);
+        request = builder.build();
+
+        koutResult = koutAPI.post(request);
+
+        Assert.assertEquals(4, koutResult.size());
+        expected = ImmutableSet.of(peterId, rippleId, lopId, joshId);
+        Assert.assertEquals(expected, koutResult.ids());
+    }
+
+    @Test
+    public void testKoutPostWithPath() {
+        Object markoId = getVertexId("person", "name", "marko");
+        Object rippleId = getVertexId("software", "name", "ripple");
+        Object joshId = getVertexId("person", "name", "josh");
+        Object lopId = getVertexId("software", "name", "lop");
+        Object vadasId = getVertexId("person", "name", "vadas");
+        Object peterId = getVertexId("person", "name", "peter");
+
+        KoutRequest.Builder builder = KoutRequest.builder();
+        builder.source(markoId);
+        builder.step().direction(Direction.BOTH);
+        builder.maxDepth(1);
+        builder.withPath(true);
+        KoutRequest request = builder.build();
+
+        Kout koutResult = koutAPI.post(request);
+
+        Assert.assertEquals(3, koutResult.size());
+        Set<Object> expected = ImmutableSet.of(vadasId, lopId, joshId);
+        Assert.assertEquals(expected, koutResult.ids());
+        Assert.assertEquals(3, koutResult.paths().size());
+        List<Object> expectedPaths = ImmutableList.of(
+                ImmutableList.of(markoId, vadasId),
+                ImmutableList.of(markoId, lopId),
+                ImmutableList.of(markoId, joshId)
+        );
+        for (Path path : koutResult.paths()) {
+            Assert.assertTrue(expectedPaths.contains(path.objects()));
+        }
+
+        builder = KoutRequest.builder();
+        builder.source(markoId);
+        builder.step().direction(Direction.BOTH);
+        builder.maxDepth(2);
+        builder.withPath(true);
+        request = builder.build();
+
+        koutResult = koutAPI.post(request);
+
+        Assert.assertEquals(2, koutResult.size());
+        expected = ImmutableSet.of(peterId, rippleId);
+        Assert.assertEquals(expected, koutResult.ids());
+        Assert.assertEquals(2, koutResult.paths().size());
+        expectedPaths = ImmutableList.of(
+                ImmutableList.of(markoId, lopId, peterId),
+                ImmutableList.of(markoId, joshId, rippleId)
+        );
+        for (Path path : koutResult.paths()) {
+            Assert.assertTrue(expectedPaths.contains(path.objects()));
+        }
+    }
+
+    @Test
+    public void testKoutPostWithVertex() {
+        Object markoId = getVertexId("person", "name", "marko");
+        Object rippleId = getVertexId("software", "name", "ripple");
+        Object joshId = getVertexId("person", "name", "josh");
+        Object lopId = getVertexId("software", "name", "lop");
+        Object vadasId = getVertexId("person", "name", "vadas");
+        Object peterId = getVertexId("person", "name", "peter");
+
+        KoutRequest.Builder builder = KoutRequest.builder();
+        builder.source(markoId);
+        builder.step().direction(Direction.BOTH);
+        builder.maxDepth(1);
+        builder.withPath(false);
+        builder.withVertex(true);
+        KoutRequest request = builder.build();
+
+        Kout koutResult = koutAPI.post(request);
+
+        Assert.assertEquals(3, koutResult.size());
+        Set<Object> expected = ImmutableSet.of(vadasId, lopId, joshId);
+        Assert.assertEquals(expected, koutResult.ids());
+        Assert.assertEquals(3, koutResult.vertices().size());
+        Set<Object> expectedVids = ImmutableSet.of(vadasId, lopId, joshId);
+        for (Vertex vertex : koutResult.vertices()) {
+            Assert.assertTrue(expectedVids.contains(vertex.id()));
+        }
+
+        builder = KoutRequest.builder();
+        builder.source(markoId);
+        builder.step().direction(Direction.BOTH);
+        builder.maxDepth(2);
+        builder.withPath(false);
+        builder.withVertex(true);
+        request = builder.build();
+
+        koutResult = koutAPI.post(request);
+
+        Assert.assertEquals(2, koutResult.size());
+        expected = ImmutableSet.of(peterId, rippleId);
+        Assert.assertEquals(expected, koutResult.ids());
+        Assert.assertEquals(2, koutResult.vertices().size());
+        expectedVids = ImmutableSet.of(peterId, rippleId);
+        for (Vertex vertex : koutResult.vertices()) {
+            Assert.assertTrue(expectedVids.contains(vertex.id()));
+        }
+
+        builder = KoutRequest.builder();
+        builder.source(markoId);
+        builder.step().direction(Direction.BOTH);
+        builder.maxDepth(1);
+        builder.withPath(true);
+        builder.withVertex(true);
+        request = builder.build();
+
+        koutResult = koutAPI.post(request);
+
+        Assert.assertEquals(3, koutResult.size());
+        expected = ImmutableSet.of(vadasId, lopId, joshId);
+        Assert.assertEquals(expected, koutResult.ids());
+        Assert.assertEquals(3, koutResult.paths().size());
+        Set<List<Object>> expectedPaths = ImmutableSet.of(
+                ImmutableList.of(markoId, vadasId),
+                ImmutableList.of(markoId, lopId),
+                ImmutableList.of(markoId, joshId)
+        );
+        for (Path path : koutResult.paths()) {
+            Assert.assertTrue(expectedPaths.contains(path.objects()));
+        }
+        Assert.assertEquals(4, koutResult.vertices().size());
+        expectedVids = ImmutableSet.of(markoId, vadasId, lopId, joshId);
+        for (Vertex vertex : koutResult.vertices()) {
+            Assert.assertTrue(expectedVids.contains(vertex.id()));
+        }
+
+        builder = KoutRequest.builder();
+        builder.source(markoId);
+        builder.step().direction(Direction.BOTH);
+        builder.maxDepth(2);
+        builder.withPath(true);
+        builder.withVertex(true);
+        request = builder.build();
+
+        koutResult = koutAPI.post(request);
+
+        Assert.assertEquals(2, koutResult.size());
+        expected = ImmutableSet.of(peterId, rippleId);
+        Assert.assertEquals(expected, koutResult.ids());
+        Assert.assertEquals(2, koutResult.paths().size());
+        expectedPaths = ImmutableSet.of(
+                ImmutableList.of(markoId, lopId, peterId),
+                ImmutableList.of(markoId, joshId, rippleId)
+        );
+        for (Path path : koutResult.paths()) {
+            Assert.assertTrue(expectedPaths.contains(path.objects()));
+        }
+        Assert.assertEquals(5, koutResult.vertices().size());
+        expectedVids = ImmutableSet.of(markoId, peterId, lopId,
+                                       joshId, rippleId);
+        for (Vertex vertex : koutResult.vertices()) {
+            Assert.assertTrue(expectedVids.contains(vertex.id()));
+        }
+    }
+
+    @Test
+    public void testKoutPostWithSingleLabel() {
+        Object markoId = getVertexId("person", "name", "marko");
+        Object joshId = getVertexId("person", "name", "josh");
+        Object lopId = getVertexId("software", "name", "lop");
+        Object vadasId = getVertexId("person", "name", "vadas");
+        Object peterId = getVertexId("person", "name", "peter");
+
+        KoutRequest.Builder builder = KoutRequest.builder();
+        builder.source(markoId);
+        builder.step().direction(Direction.BOTH).labels("created");
+        builder.maxDepth(1);
+        KoutRequest request = builder.build();
+
+        Kout koutResult = koutAPI.post(request);
+
+        Assert.assertEquals(1, koutResult.size());
+        Set<Object> expected = ImmutableSet.of(lopId);
+        Assert.assertEquals(expected, koutResult.ids());
+
+        builder = KoutRequest.builder();
+        builder.source(markoId);
+        builder.step().direction(Direction.BOTH).labels("created");
+        builder.maxDepth(2);
+        request = builder.build();
+
+        koutResult = koutAPI.post(request);
+
+        Assert.assertEquals(2, koutResult.size());
+        expected = ImmutableSet.of(peterId, joshId);
+        Assert.assertEquals(expected, koutResult.ids());
+
+        builder = KoutRequest.builder();
+        builder.source(markoId);
+        builder.step().direction(Direction.BOTH).labels("knows");
+        builder.maxDepth(1);
+        request = builder.build();
+
+        koutResult = koutAPI.post(request);
+
+        Assert.assertEquals(2, koutResult.size());
+        expected = ImmutableSet.of(vadasId, joshId);
+        Assert.assertEquals(expected, koutResult.ids());
+
+        builder = KoutRequest.builder();
+        builder.source(markoId);
+        builder.step().direction(Direction.BOTH).labels("knows");
+        builder.maxDepth(2);
+        request = builder.build();
+
+        koutResult = koutAPI.post(request);
+
+        Assert.assertEquals(0, koutResult.size());
+    }
+
+    @Test
+    public void testKoutPostWithMultiLabels() {
+        Object markoId = getVertexId("person", "name", "marko");
+        Object joshId = getVertexId("person", "name", "josh");
+        Object lopId = getVertexId("software", "name", "lop");
+        Object vadasId = getVertexId("person", "name", "vadas");
+        Object peterId = getVertexId("person", "name", "peter");
+        Object rippleId = getVertexId("software", "name", "ripple");
+
+        KoutRequest.Builder builder = KoutRequest.builder();
+        builder.source(markoId);
+        builder.step().direction(Direction.BOTH)
+               .labels("knows").labels("created");
+        builder.maxDepth(1);
+        KoutRequest request = builder.build();
+
+        Kout koutResult = koutAPI.post(request);
+
+        Assert.assertEquals(3, koutResult.size());
+        Set<Object> expected = ImmutableSet.of(vadasId, joshId, lopId);
+        Assert.assertEquals(expected, koutResult.ids());
+        
+        builder = KoutRequest.builder();
+        builder.source(markoId);
+        builder.step().direction(Direction.BOTH)
+               .labels("knows").labels("created");
+        builder.maxDepth(2);
+        request = builder.build();
+
+        koutResult = koutAPI.post(request);
+
+        Assert.assertEquals(2, koutResult.size());
+        expected = ImmutableSet.of(peterId, rippleId);
+        Assert.assertEquals(expected, koutResult.ids());
+    }
+
+    @Test
+    public void testKoutPostWithDirection() {
+        Object markoId = getVertexId("person", "name", "marko");
+        Object rippleId = getVertexId("software", "name", "ripple");
+        Object joshId = getVertexId("person", "name", "josh");
+        Object lopId = getVertexId("software", "name", "lop");
+        Object vadasId = getVertexId("person", "name", "vadas");
+
+        KoutRequest.Builder builder = KoutRequest.builder();
+        builder.source(markoId);
+        builder.step().direction(Direction.OUT);
+        builder.maxDepth(1);
+        KoutRequest request = builder.build();
+
+        Kout koutResult = koutAPI.post(request);
+
+        Assert.assertEquals(3, koutResult.size());
+        Set<Object> expected = ImmutableSet.of(vadasId, lopId, joshId);
+        Assert.assertEquals(expected, koutResult.ids());
+
+        builder = KoutRequest.builder();
+        builder.source(markoId);
+        builder.step().direction(Direction.OUT);
+        builder.maxDepth(2);
+        request = builder.build();
+
+        koutResult = koutAPI.post(request);
+
+        Assert.assertEquals(1, koutResult.size());
+        expected = ImmutableSet.of(rippleId);
+        Assert.assertEquals(expected, koutResult.ids());
+    }
+
+    @Test
+    public void testKoutPostWithProperties() {
+        Object markoId = getVertexId("person", "name", "marko");
+        Object rippleId = getVertexId("software", "name", "ripple");
+        Object joshId = getVertexId("person", "name", "josh");
+        Object lopId = getVertexId("software", "name", "lop");
+        Object peterId = getVertexId("person", "name", "peter");
+
+        KoutRequest.Builder builder = KoutRequest.builder();
+        builder.source(markoId);
+        builder.step().direction(Direction.BOTH)
+               .properties("date", "P.gt(\"2014-01-01 00:00:00\")");
+        builder.maxDepth(1);
+        KoutRequest request = builder.build();
+
+        Kout koutResult = koutAPI.post(request);
+
+        Assert.assertEquals(1, koutResult.size());
+        Set<Object> expected = ImmutableSet.of(lopId);
+        Assert.assertEquals(expected, koutResult.ids());
+
+        builder = KoutRequest.builder();
+        builder.source(markoId);
+        builder.step().direction(Direction.BOTH)
+               .properties("date", "P.gt(\"2014-01-01 00:00:00\")");
+        builder.maxDepth(2);
+        request = builder.build();
+
+        koutResult = koutAPI.post(request);
+
+        Assert.assertEquals(2, koutResult.size());
+        expected = ImmutableSet.of(peterId, joshId);
+        Assert.assertEquals(expected, koutResult.ids());
+
+        builder = KoutRequest.builder();
+        builder.source(markoId);
+        builder.step().direction(Direction.BOTH)
+               .properties("date", "P.gt(\"2014-01-01 00:00:00\")");
+        builder.maxDepth(3);
+        request = builder.build();
+
+        koutResult = koutAPI.post(request);
+
+        Assert.assertEquals(1, koutResult.size());
+        expected = ImmutableSet.of(rippleId);
+        Assert.assertEquals(expected, koutResult.ids());
+    }
+
+    @Test
+    public void testKoutPostWithLimit() {
+        Object markoId = getVertexId("person", "name", "marko");
+        Object rippleId = getVertexId("software", "name", "ripple");
+        Object joshId = getVertexId("person", "name", "josh");
+        Object lopId = getVertexId("software", "name", "lop");
+        Object vadasId = getVertexId("person", "name", "vadas");
+        Object peterId = getVertexId("person", "name", "peter");
+
+        KoutRequest.Builder builder = KoutRequest.builder();
+        builder.source(markoId);
+        builder.step().direction(Direction.BOTH);
+        builder.maxDepth(1);
+        builder.limit(2);
+        KoutRequest request = builder.build();
+
+        Kout koutResult = koutAPI.post(request);
+
+        Assert.assertEquals(2, koutResult.size());
+        Set<Object> expected = ImmutableSet.of(vadasId, lopId, joshId);
+        Assert.assertTrue(expected.containsAll(koutResult.ids()));
+
+        builder = KoutRequest.builder();
+        builder.source(markoId);
+        builder.step().direction(Direction.BOTH);
+        builder.maxDepth(2);
+        builder.limit(1);
+        request = builder.build();
+
+        koutResult = koutAPI.post(request);
+
+        Assert.assertEquals(1, koutResult.size());
+        expected = ImmutableSet.of(peterId, rippleId);
+        Assert.assertTrue(expected.containsAll(koutResult.ids()));
+    }
+
+    @Test
+    public void testKoutPostWithCountOnly() {
+        Object markoId = getVertexId("person", "name", "marko");
+
+        KoutRequest.Builder builder = KoutRequest.builder();
+        builder.source(markoId);
+        builder.step().direction(Direction.BOTH);
+        builder.maxDepth(1);
+        builder.countOnly(true);
+        KoutRequest request = builder.build();
+
+        Kout koutResult = koutAPI.post(request);
+
+        Assert.assertEquals(3, koutResult.size());
+        Assert.assertTrue(koutResult.ids().isEmpty());
+        Assert.assertTrue(koutResult.paths().isEmpty());
+        Assert.assertTrue(koutResult.vertices().isEmpty());
+
+        builder = KoutRequest.builder();
+        builder.source(markoId);
+        builder.step().direction(Direction.BOTH);
+        builder.maxDepth(2);
+        builder.countOnly(true);
+        request = builder.build();
+
+        koutResult = koutAPI.post(request);
+
+        Assert.assertEquals(2, koutResult.size());
+        Assert.assertTrue(koutResult.ids().isEmpty());
+        Assert.assertTrue(koutResult.paths().isEmpty());
+        Assert.assertTrue(koutResult.vertices().isEmpty());
+
+        builder = KoutRequest.builder();
+        builder.source(markoId);
+        builder.step().direction(Direction.BOTH);
+        builder.maxDepth(1);
+        builder.countOnly(true);
+        builder.withPath(true);
+
+        KoutRequest.Builder finalBuilder = builder;
+        Assert.assertThrows(IllegalArgumentException.class, ()-> {
+            finalBuilder.build();
+        });
+
+        builder = KoutRequest.builder();
+        builder.source(markoId);
+        builder.step().direction(Direction.BOTH);
+        builder.maxDepth(1);
+        builder.countOnly(true);
+        builder.withVertex(true);
+
+        KoutRequest.Builder finalBuilder1 = builder;
+        Assert.assertThrows(IllegalArgumentException.class, ()-> {
+            finalBuilder1.build();
+        });
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/api/traverser/MultiNodeShortestPathApiTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/traverser/MultiNodeShortestPathApiTest.java
new file mode 100644
index 0000000..2c8fec0
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/traverser/MultiNodeShortestPathApiTest.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.traverser;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.baidu.hugegraph.api.BaseApiTest;
+import com.baidu.hugegraph.structure.constant.Direction;
+import com.baidu.hugegraph.structure.graph.Vertex;
+import com.baidu.hugegraph.structure.traverser.MultiNodeShortestPathRequest;
+import com.baidu.hugegraph.structure.traverser.PathsWithVertices;
+import com.baidu.hugegraph.testutil.Assert;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+
+public class MultiNodeShortestPathApiTest extends TraverserApiTest {
+
+    @BeforeClass
+    public static void prepareSchemaAndGraph() {
+        BaseApiTest.initPropertyKey();
+        BaseApiTest.initVertexLabel();
+        BaseApiTest.initEdgeLabel();
+        BaseApiTest.initIndexLabel();
+        BaseApiTest.initVertex();
+        BaseApiTest.initEdge();
+    }
+
+    @Test
+    public void testMultiNodeShortestPath() {
+        Object markoId = getVertexId("person", "name", "marko");
+        Object rippleId = getVertexId("software", "name", "ripple");
+        Object joshId = getVertexId("person", "name", "josh");
+        Object lopId = getVertexId("software", "name", "lop");
+        Object vadasId = getVertexId("person", "name", "vadas");
+        Object peterId = getVertexId("person", "name", "peter");
+
+        MultiNodeShortestPathRequest.Builder builder =
+                MultiNodeShortestPathRequest.builder();
+        builder.vertices().ids(markoId, rippleId, joshId,
+                               lopId, vadasId, peterId);
+        builder.step().direction(Direction.BOTH);
+
+        MultiNodeShortestPathRequest request = builder.build();
+        PathsWithVertices pathsWithVertices =
+                          multiNodeShortestPathAPI.post(request);
+        List<PathsWithVertices.Paths> paths = pathsWithVertices.paths();
+        Assert.assertEquals(15, paths.size());
+
+        List<List<Object>> expected = ImmutableList.of(
+                ImmutableList.of(markoId, joshId),
+                ImmutableList.of(peterId, lopId),
+                ImmutableList.of(peterId, lopId, markoId, vadasId),
+                ImmutableList.of(peterId, lopId, joshId),
+                ImmutableList.of(markoId, vadasId),
+                ImmutableList.of(vadasId, markoId, lopId),
+                ImmutableList.of(vadasId, markoId, joshId),
+                ImmutableList.of(peterId, lopId, joshId, rippleId),
+                ImmutableList.of(lopId, joshId, rippleId),
+                ImmutableList.of(lopId, joshId),
+                ImmutableList.of(rippleId, joshId),
+                ImmutableList.of(markoId, lopId),
+                ImmutableList.of(markoId, lopId, peterId),
+                ImmutableList.of(markoId, joshId, rippleId),
+                ImmutableList.of(vadasId, markoId, joshId, rippleId)
+        );
+        for (PathsWithVertices.Paths path : paths) {
+            Assert.assertTrue(contains(expected, path));
+        }
+    }
+
+    @Test
+    public void testMultiNodeShortestPathWithMaxDepth() {
+        Object markoId = getVertexId("person", "name", "marko");
+        Object rippleId = getVertexId("software", "name", "ripple");
+        Object joshId = getVertexId("person", "name", "josh");
+        Object lopId = getVertexId("software", "name", "lop");
+        Object vadasId = getVertexId("person", "name", "vadas");
+        Object peterId = getVertexId("person", "name", "peter");
+
+        MultiNodeShortestPathRequest.Builder builder =
+                MultiNodeShortestPathRequest.builder();
+        builder.vertices().ids(markoId, rippleId, joshId,
+                               lopId, vadasId, peterId);
+        builder.step().direction(Direction.BOTH);
+        builder.maxDepth(2);
+
+        MultiNodeShortestPathRequest request = builder.build();
+        PathsWithVertices pathsWithVertices =
+                          multiNodeShortestPathAPI.post(request);
+        List<PathsWithVertices.Paths> paths = pathsWithVertices.paths();
+        Assert.assertEquals(12, paths.size());
+
+        List<List<Object>> expected = ImmutableList.of(
+                ImmutableList.of(markoId, joshId),
+                ImmutableList.of(peterId, lopId),
+                ImmutableList.of(peterId, lopId, joshId),
+                ImmutableList.of(markoId, vadasId),
+                ImmutableList.of(vadasId, markoId, lopId),
+                ImmutableList.of(vadasId, markoId, joshId),
+                ImmutableList.of(lopId, joshId, rippleId),
+                ImmutableList.of(lopId, joshId),
+                ImmutableList.of(rippleId, joshId),
+                ImmutableList.of(markoId, lopId),
+                ImmutableList.of(markoId, lopId, peterId),
+                ImmutableList.of(markoId, joshId, rippleId)
+        );
+        for (PathsWithVertices.Paths path : paths) {
+            Assert.assertTrue(contains(expected, path));
+        }
+
+        builder = MultiNodeShortestPathRequest.builder();
+        builder.vertices().ids(markoId, rippleId, joshId,
+                               lopId, vadasId, peterId);
+        builder.step().direction(Direction.BOTH);
+        builder.maxDepth(1);
+
+        request = builder.build();
+        pathsWithVertices = multiNodeShortestPathAPI.post(request);
+        paths = pathsWithVertices.paths();
+        Assert.assertEquals(6, paths.size());
+
+        expected = ImmutableList.of(
+                ImmutableList.of(markoId, joshId),
+                ImmutableList.of(peterId, lopId),
+                ImmutableList.of(markoId, vadasId),
+                ImmutableList.of(lopId, joshId),
+                ImmutableList.of(rippleId, joshId),
+                ImmutableList.of(markoId, lopId)
+        );
+        for (PathsWithVertices.Paths path : paths) {
+            Assert.assertTrue(contains(expected, path));
+        }
+    }
+
+    @Test
+    public void testMultiNodeShortestPathWithVertex() {
+        Object markoId = getVertexId("person", "name", "marko");
+        Object rippleId = getVertexId("software", "name", "ripple");
+        Object joshId = getVertexId("person", "name", "josh");
+        Object lopId = getVertexId("software", "name", "lop");
+        Object vadasId = getVertexId("person", "name", "vadas");
+        Object peterId = getVertexId("person", "name", "peter");
+
+        MultiNodeShortestPathRequest.Builder builder =
+                MultiNodeShortestPathRequest.builder();
+        builder.vertices().ids(markoId, rippleId, joshId,
+                               lopId, vadasId, peterId);
+        builder.step().direction(Direction.BOTH);
+        builder.withVertex(true);
+
+        MultiNodeShortestPathRequest request = builder.build();
+        PathsWithVertices pathsWithVertices =
+                          multiNodeShortestPathAPI.post(request);
+        List<PathsWithVertices.Paths> paths = pathsWithVertices.paths();
+        Assert.assertEquals(15, paths.size());
+
+        List<List<Object>> expected = ImmutableList.of(
+                ImmutableList.of(markoId, joshId),
+                ImmutableList.of(peterId, lopId),
+                ImmutableList.of(peterId, lopId, markoId, vadasId),
+                ImmutableList.of(peterId, lopId, joshId),
+                ImmutableList.of(markoId, vadasId),
+                ImmutableList.of(vadasId, markoId, lopId),
+                ImmutableList.of(vadasId, markoId, joshId),
+                ImmutableList.of(peterId, lopId, joshId, rippleId),
+                ImmutableList.of(lopId, joshId, rippleId),
+                ImmutableList.of(lopId, joshId),
+                ImmutableList.of(rippleId, joshId),
+                ImmutableList.of(markoId, lopId),
+                ImmutableList.of(markoId, lopId, peterId),
+                ImmutableList.of(markoId, joshId, rippleId),
+                ImmutableList.of(vadasId, markoId, joshId, rippleId)
+        );
+        for (PathsWithVertices.Paths path : paths) {
+            Assert.assertTrue(contains(expected, path));
+        }
+
+        Set<Vertex> vertices = pathsWithVertices.vertices();
+        Assert.assertEquals(6, vertices.size());
+        Set<Object> vertexIds = ImmutableSet.of(markoId, rippleId, joshId,
+                                                lopId, vadasId, peterId);
+        for (Vertex vertex : vertices) {
+            Assert.assertTrue(vertexIds.contains(vertex.id()));
+        }
+    }
+
+    private static boolean contains(List<List<Object>> expected,
+                                    PathsWithVertices.Paths path) {
+        List<Object> objects = path.objects();
+        if (expected.contains(objects)) {
+            return true;
+        }
+        Collections.reverse(objects);
+        return expected.contains(objects);
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/api/traverser/NeighborRankApiTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/traverser/NeighborRankApiTest.java
new file mode 100644
index 0000000..540219a
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/traverser/NeighborRankApiTest.java
@@ -0,0 +1,414 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.traverser;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.baidu.hugegraph.api.BaseApiTest;
+import com.baidu.hugegraph.driver.GraphManager;
+import com.baidu.hugegraph.driver.SchemaManager;
+import com.baidu.hugegraph.exception.ServerException;
+import com.baidu.hugegraph.structure.constant.Direction;
+import com.baidu.hugegraph.structure.constant.T;
+import com.baidu.hugegraph.structure.graph.Vertex;
+import com.baidu.hugegraph.structure.traverser.Ranks;
+import com.baidu.hugegraph.testutil.Assert;
+import com.google.common.collect.ImmutableMap;
+
+public class NeighborRankApiTest extends TraverserApiTest {
+
+    @BeforeClass
+    public static void initNeighborRankGraph() {
+        GraphManager graph = graph();
+        SchemaManager schema = schema();
+
+        schema.propertyKey("name").asText().ifNotExist().create();
+
+        schema.vertexLabel("person")
+              .properties("name")
+              .useCustomizeStringId()
+              .ifNotExist()
+              .create();
+
+        schema.vertexLabel("movie")
+              .properties("name")
+              .useCustomizeStringId()
+              .ifNotExist()
+              .create();
+
+        schema.edgeLabel("follow")
+              .sourceLabel("person")
+              .targetLabel("person")
+              .ifNotExist()
+              .create();
+
+        schema.edgeLabel("like")
+              .sourceLabel("person")
+              .targetLabel("movie")
+              .ifNotExist()
+              .create();
+
+        schema.edgeLabel("directedBy")
+              .sourceLabel("movie")
+              .targetLabel("person")
+              .ifNotExist()
+              .create();
+
+        Vertex O = graph.addVertex(T.label, "person", T.id, "O", "name", "O");
+
+        Vertex A = graph.addVertex(T.label, "person", T.id, "A", "name", "A");
+        Vertex B = graph.addVertex(T.label, "person", T.id, "B", "name", "B");
+        Vertex C = graph.addVertex(T.label, "person", T.id, "C", "name", "C");
+        Vertex D = graph.addVertex(T.label, "person", T.id, "D", "name", "D");
+
+        Vertex E = graph.addVertex(T.label, "movie", T.id, "E", "name", "E");
+        Vertex F = graph.addVertex(T.label, "movie", T.id, "F", "name", "F");
+        Vertex G = graph.addVertex(T.label, "movie", T.id, "G", "name", "G");
+        Vertex H = graph.addVertex(T.label, "movie", T.id, "H", "name", "H");
+        Vertex I = graph.addVertex(T.label, "movie", T.id, "I", "name", "I");
+        Vertex J = graph.addVertex(T.label, "movie", T.id, "J", "name", "J");
+
+        Vertex K = graph.addVertex(T.label, "person", T.id, "K", "name", "K");
+        Vertex L = graph.addVertex(T.label, "person", T.id, "L", "name", "L");
+        Vertex M = graph.addVertex(T.label, "person", T.id, "M", "name", "M");
+
+        O.addEdge("follow", A);
+        O.addEdge("follow", B);
+        O.addEdge("follow", C);
+        D.addEdge("follow", O);
+
+        A.addEdge("follow", B);
+        A.addEdge("like", E);
+        A.addEdge("like", F);
+
+        B.addEdge("like", G);
+        B.addEdge("like", H);
+
+        C.addEdge("like", I);
+        C.addEdge("like", J);
+
+        E.addEdge("directedBy", K);
+        F.addEdge("directedBy", B);
+        F.addEdge("directedBy", L);
+
+        G.addEdge("directedBy", M);
+    }
+
+    @AfterClass
+    public static void clearNeighborRankGraph() {
+        List<Long> taskIds = new ArrayList<>();
+        taskIds.add(edgeLabelAPI.delete("directedBy"));
+        taskIds.add(edgeLabelAPI.delete("like"));
+        taskIds.add(edgeLabelAPI.delete("follow"));
+        taskIds.forEach(BaseApiTest::waitUntilTaskCompleted);
+        taskIds.clear();
+        taskIds.add(vertexLabelAPI.delete("movie"));
+        taskIds.add(vertexLabelAPI.delete("person"));
+        taskIds.forEach(BaseApiTest::waitUntilTaskCompleted);
+    }
+
+    @Test
+    public void testNeighborRank() {
+        NeighborRankAPI.Request.Builder builder;
+        builder = NeighborRankAPI.Request.builder();
+        builder.source("O");
+        builder.steps().direction(Direction.OUT).degree(-1).top(10);
+        builder.steps().direction(Direction.OUT).degree(-1).top(10);
+        builder.steps().direction(Direction.OUT).degree(-1).top(10);
+        builder.alpha(0.9).capacity(-1);
+        NeighborRankAPI.Request request = builder.build();
+
+        List<Ranks> ranks = neighborRankAPI.post(request);
+        Assert.assertEquals(4, ranks.size());
+        Assert.assertEquals(ImmutableMap.of("O", 1.0D), ranks.get(0));
+        Assert.assertEquals(ImmutableMap.of("B", 0.4305D, "A", 0.3D, "C", 0.3D),
+                            ranks.get(1));
+        Assert.assertEquals(ImmutableMap.builder()
+                                        .put("G", 0.17550000000000002D)
+                                        .put("H", 0.17550000000000002D)
+                                        .put("I", 0.135D)
+                                        .put("J", 0.135D)
+                                        .put("E", 0.09000000000000001D)
+                                        .put("F", 0.09000000000000001D)
+                                        .build(),
+                            ranks.get(2));
+        Assert.assertEquals(ImmutableMap.of("M", 0.15795D,
+                                            "K", 0.08100000000000002D,
+                                            "L", 0.04050000000000001D),
+                            ranks.get(3));
+    }
+
+    @Test
+    public void testNeighborRankWithOtherAlpha() {
+        NeighborRankAPI.Request.Builder builder;
+        builder = NeighborRankAPI.Request.builder();
+        builder.source("O");
+        builder.steps().direction(Direction.OUT).degree(-1).top(10);
+        builder.steps().direction(Direction.OUT).degree(-1).top(10);
+        builder.steps().direction(Direction.OUT).degree(-1).top(10);
+        builder.alpha(1.0).capacity(-1);
+        NeighborRankAPI.Request request = builder.build();
+
+        List<Ranks> ranks = neighborRankAPI.post(request);
+        Assert.assertEquals(4, ranks.size());
+        Assert.assertEquals(ImmutableMap.of("O", 1.0D), ranks.get(0));
+        Assert.assertEquals(ImmutableMap.of("B", 0.5D,
+                                            "A", 0.3333333333333333D,
+                                            "C", 0.3333333333333333D),
+                            ranks.get(1));
+        Assert.assertEquals(ImmutableMap.builder()
+                                        .put("G", 0.2222222222222222D)
+                                        .put("H", 0.2222222222222222D)
+                                        .put("I", 0.16666666666666666D)
+                                        .put("J", 0.16666666666666666D)
+                                        .put("E", 0.1111111111111111D)
+                                        .put("F", 0.1111111111111111D)
+                                        .build(),
+                            ranks.get(2));
+        Assert.assertEquals(ImmutableMap.of("M", 0.2222222222222222D,
+                                            "K", 0.1111111111111111D,
+                                            "L", 0.05555555555555555D),
+                            ranks.get(3));
+    }
+
+    @Test
+    public void testNeighborRankWithDirection() {
+        NeighborRankAPI.Request.Builder builder;
+        builder = NeighborRankAPI.Request.builder();
+        builder.source("O");
+        builder.steps().direction(Direction.BOTH);
+        builder.steps().direction(Direction.IN);
+        builder.steps().direction(Direction.OUT);
+        builder.alpha(0.9).capacity(-1);
+        NeighborRankAPI.Request request = builder.build();
+
+        List<Ranks> ranks = neighborRankAPI.post(request);
+        Assert.assertEquals(4, ranks.size());
+        Assert.assertEquals(ImmutableMap.of("O", 1.0D), ranks.get(0));
+        Assert.assertEquals(ImmutableMap.of("A", 0.32625000000000004D,
+                                            "B", 0.27056250000000004D,
+                                            "C", 0.225D,
+                                            "D", 0.225D),
+                            ranks.get(1));
+        Assert.assertEquals(ImmutableMap.of("F", 0.10125D),
+                            ranks.get(2));
+        Assert.assertEquals(ImmutableMap.of("L", 0.045562500000000006D),
+                            ranks.get(3));
+    }
+
+    @Test
+    public void testNeighborRankWithLabels() {
+        NeighborRankAPI.Request.Builder builder;
+        builder = NeighborRankAPI.Request.builder();
+        builder.source("O");
+        builder.steps().labels("follow").direction(Direction.OUT);
+        builder.steps().labels("like").direction(Direction.OUT);
+        builder.steps().labels("directedBy").direction(Direction.OUT);
+        builder.alpha(0.9).capacity(-1);
+        NeighborRankAPI.Request request = builder.build();
+
+        List<Ranks> ranks = neighborRankAPI.post(request);
+        Assert.assertEquals(4, ranks.size());
+        Assert.assertEquals(ImmutableMap.of("O", 1.0D), ranks.get(0));
+        Assert.assertEquals(ImmutableMap.of("B", 0.36075D,
+                                            "A", 0.3D,
+                                            "C", 0.3D),
+                            ranks.get(1));
+        Assert.assertEquals(ImmutableMap.builder()
+                                        .put("E", 0.135)
+                                        .put("F", 0.135)
+                                        .put("G", 0.135)
+                                        .put("H", 0.135)
+                                        .put("I", 0.135)
+                                        .put("J", 0.135)
+                                        .build(),
+                            ranks.get(2));
+        Assert.assertEquals(ImmutableMap.of("K", 0.12150000000000001D,
+                                            "M", 0.12150000000000001D,
+                                            "L", 0.060750000000000005D),
+                            ranks.get(3));
+    }
+
+    @Test
+    public void testNeighborRankWithTop() {
+        NeighborRankAPI.Request.Builder builder;
+        builder = NeighborRankAPI.Request.builder();
+        builder.source("O");
+        builder.steps().direction(Direction.OUT).degree(-1).top(2);
+        builder.steps().direction(Direction.OUT).degree(-1).top(3);
+        builder.steps().direction(Direction.OUT).degree(-1).top(2);
+        builder.alpha(0.9).capacity(-1);
+        NeighborRankAPI.Request request = builder.build();
+
+        List<Ranks> ranks = neighborRankAPI.post(request);
+        Assert.assertEquals(4, ranks.size());
+        Assert.assertEquals(ImmutableMap.of("O", 1.0D), ranks.get(0));
+        Assert.assertEquals(ImmutableMap.of("B", 0.4305D, "A", 0.3D),
+                            ranks.get(1));
+        Assert.assertEquals(ImmutableMap.of("G", 0.17550000000000002D,
+                                            "H", 0.17550000000000002D,
+                                            "I", 0.135D),
+                            ranks.get(2));
+        Assert.assertEquals(ImmutableMap.of("M", 0.15795D,
+                                            "K", 0.08100000000000002D),
+                            ranks.get(3));
+    }
+
+    @Test
+    public void testNeighborRankWithDegree() {
+        NeighborRankAPI.Request.Builder builder;
+        builder = NeighborRankAPI.Request.builder();
+        builder.source("O");
+        builder.steps().direction(Direction.OUT).degree(2);
+        builder.steps().direction(Direction.OUT).degree(1);
+        builder.steps().direction(Direction.OUT).degree(1);
+        builder.alpha(0.9).capacity(-1);
+        NeighborRankAPI.Request request = builder.build();
+
+        List<Ranks> ranks = neighborRankAPI.post(request);
+        Assert.assertEquals(4, ranks.size());
+        Assert.assertEquals(ImmutableMap.of("O", 1.0D), ranks.get(0));
+        Assert.assertEquals(ImmutableMap.of("B", 0.855D, "A", 0.45D),
+                            ranks.get(1));
+        Assert.assertEquals(ImmutableMap.of("G", 0.7695D), ranks.get(2));
+        Assert.assertEquals(ImmutableMap.of("M", 0.69255D), ranks.get(3));
+
+        builder = NeighborRankAPI.Request.builder();
+        builder.source("O");
+        builder.steps().direction(Direction.OUT).degree(2);
+        builder.steps().direction(Direction.OUT).degree(2);
+        builder.steps().direction(Direction.OUT).degree(1);
+        builder.alpha(0.9).capacity(-1);
+        request = builder.build();
+
+        ranks = neighborRankAPI.post(request);
+        Assert.assertEquals(4, ranks.size());
+        Assert.assertEquals(ImmutableMap.of("O", 1.0D), ranks.get(0));
+        Assert.assertEquals(ImmutableMap.of("B", 0.6525000000000001D,
+                                            "A", 0.45D),
+                            ranks.get(1));
+        Assert.assertEquals(ImmutableMap.of("G", 0.293625D,
+                                            "H", 0.293625D,
+                                            "E", 0.2025D),
+                            ranks.get(2));
+        Assert.assertEquals(ImmutableMap.of("M", 0.2642625D,
+                                            "K", 0.18225000000000002D),
+                            ranks.get(3));
+    }
+
+    @Test
+    public void testNeighborRankWithCapacity() {
+        NeighborRankAPI.Request.Builder builder;
+        builder = NeighborRankAPI.Request.builder();
+        builder.source("O");
+        builder.steps().direction(Direction.OUT);
+        builder.steps().direction(Direction.OUT);
+        builder.steps().direction(Direction.OUT);
+        builder.alpha(0.9).capacity(1);
+        NeighborRankAPI.Request request = builder.build();
+
+        Assert.assertThrows(ServerException.class, () -> {
+            neighborRankAPI.post(request);
+        }, e -> {
+            String expect = "Exceed capacity '1' while finding neighbor rank";
+            Assert.assertContains(expect, e.getMessage());
+        });
+    }
+
+    @Test
+    public void testNeighborRankWithIsolatedVertex() {
+        Vertex isolate = graph().addVertex(T.label, "person", T.id, "isolate",
+                                           "name", "isolate-vertex");
+
+        NeighborRankAPI.Request.Builder builder;
+        builder = NeighborRankAPI.Request.builder();
+        builder.source("isolate").alpha(0.9);
+        builder.steps().direction(Direction.BOTH);
+        NeighborRankAPI.Request request = builder.build();
+
+        List<Ranks> ranks = neighborRankAPI.post(request);
+        Assert.assertEquals(2, ranks.size());
+        Assert.assertEquals(ImmutableMap.of("isolate", 1.0D), ranks.get(0));
+        Assert.assertEquals(ImmutableMap.of(), ranks.get(1));
+
+        graph().removeVertex(isolate.id());
+    }
+
+    @Test
+    public void testNeighborRankWithInvalidParams() {
+        // Invalid source
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            NeighborRankAPI.Request.Builder builder;
+            builder = NeighborRankAPI.Request.builder();
+            builder.source(null);
+        });
+
+        // Invalid degree
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            NeighborRankAPI.Request.Builder builder;
+            builder = NeighborRankAPI.Request.builder();
+            builder.steps().degree(-2);
+        });
+
+        // Invalid top
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            NeighborRankAPI.Request.Builder builder;
+            builder = NeighborRankAPI.Request.builder();
+            builder.steps().top(0);
+        });
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            NeighborRankAPI.Request.Builder builder;
+            builder = NeighborRankAPI.Request.builder();
+            builder.steps().top(1001);
+        });
+
+        // Invalid alpha
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            NeighborRankAPI.Request.Builder builder;
+            builder = NeighborRankAPI.Request.builder();
+            builder.alpha(0.0);
+        });
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            NeighborRankAPI.Request.Builder builder;
+            builder = NeighborRankAPI.Request.builder();
+            builder.alpha(1.1);
+        });
+
+        // Invalid capacity
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            NeighborRankAPI.Request.Builder builder;
+            builder = NeighborRankAPI.Request.builder();
+            builder.capacity(-2);
+        });
+
+        // Without steps
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            NeighborRankAPI.Request.Builder builder;
+            builder = NeighborRankAPI.Request.builder();
+            builder.source("A");
+            builder.build();
+        });
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/api/traverser/PathsApiTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/traverser/PathsApiTest.java
new file mode 100644
index 0000000..e281a1c
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/traverser/PathsApiTest.java
@@ -0,0 +1,352 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.traverser;
+
+import java.util.List;
+import java.util.Set;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.baidu.hugegraph.api.BaseApiTest;
+import com.baidu.hugegraph.exception.ServerException;
+import com.baidu.hugegraph.structure.constant.Direction;
+import com.baidu.hugegraph.structure.constant.T;
+import com.baidu.hugegraph.structure.graph.Path;
+import com.baidu.hugegraph.structure.graph.Vertex;
+import com.baidu.hugegraph.structure.traverser.PathsRequest;
+import com.baidu.hugegraph.structure.traverser.PathsWithVertices;
+import com.baidu.hugegraph.testutil.Assert;
+import com.google.common.collect.ImmutableList;
+
+public class PathsApiTest extends TraverserApiTest {
+
+    @BeforeClass
+    public static void prepareSchemaAndGraph() {
+        BaseApiTest.initPropertyKey();
+        BaseApiTest.initVertexLabel();
+        BaseApiTest.initEdgeLabel();
+        BaseApiTest.initIndexLabel();
+        BaseApiTest.initVertex();
+        BaseApiTest.initEdge();
+    }
+
+    @Test
+    public void testPathsGet() {
+        Object markoId = getVertexId("person", "name", "marko");
+        Object joshId = getVertexId("person", "name", "josh");
+        Object lopId = getVertexId("software", "name", "lop");
+        Object rippleId = getVertexId("software", "name", "ripple");
+
+        List<Path> paths = pathsAPI.get(markoId, rippleId, Direction.BOTH,
+                                        null, 3, -1L, -1L, 10);
+        Assert.assertEquals(2, paths.size());
+        List<Object> path1 = ImmutableList.of(markoId, joshId, rippleId);
+        List<Object> path2 = ImmutableList.of(markoId, lopId, joshId, rippleId);
+        List<List<Object>> expectedPaths = ImmutableList.of(path1, path2);
+        Assert.assertTrue(expectedPaths.contains(paths.get(0).objects()));
+        Assert.assertTrue(expectedPaths.contains(paths.get(1).objects()));
+    }
+
+    @Test
+    public void testPathsGetWithLimit() {
+        Object markoId = getVertexId("person", "name", "marko");
+        Object joshId = getVertexId("person", "name", "josh");
+        Object rippleId = getVertexId("software", "name", "ripple");
+
+        List<Path> paths = pathsAPI.get(markoId, rippleId, Direction.BOTH,
+                                        null, 3, -1L, -1L, 1);
+        Assert.assertEquals(1, paths.size());
+        List<Object> path1 = ImmutableList.of(markoId, joshId, rippleId);
+        Assert.assertEquals(path1, paths.get(0).objects());
+    }
+
+    @Test
+    public void testPathsGetWithCapacity() {
+        Object markoId = getVertexId("person", "name", "marko");
+        Object rippleId = getVertexId("software", "name", "ripple");
+
+        Assert.assertThrows(ServerException.class, () -> {
+            pathsAPI.get(markoId, rippleId, Direction.BOTH,
+                         null, 3, -1L, 2L, 1);
+        }, e -> {
+            String expect = "Exceed capacity '2' while finding paths";
+            Assert.assertContains(expect, e.getMessage());
+        });
+    }
+
+    @Test
+    public void testPathsPost() {
+        Object markoId = getVertexId("person", "name", "marko");
+        Object rippleId = getVertexId("software", "name", "ripple");
+        Object joshId = getVertexId("person", "name", "josh");
+        Object lopId = getVertexId("software", "name", "lop");
+
+        PathsRequest.Builder builder = PathsRequest.builder();
+        builder.sources().ids(markoId);
+        builder.targets().ids(rippleId);
+        builder.step().direction(Direction.BOTH);
+        builder.maxDepth(3);
+        PathsRequest request = builder.build();
+
+        PathsWithVertices pathsWithVertices = pathsAPI.post(request);
+
+        List<PathsWithVertices.Paths> paths = pathsWithVertices.paths();
+        Assert.assertEquals(2, paths.size());
+        List<List<Object>> expected = ImmutableList.of(
+                ImmutableList.of(markoId, lopId, joshId, rippleId),
+                ImmutableList.of(markoId, joshId, rippleId)
+        );
+        for (PathsWithVertices.Paths path : paths) {
+            Assert.assertTrue(expected.contains(path.objects()));
+        }
+    }
+
+    @Test
+    public void testPathsPostWithVertex() {
+        Object markoId = getVertexId("person", "name", "marko");
+        Object rippleId = getVertexId("software", "name", "ripple");
+        Object joshId = getVertexId("person", "name", "josh");
+        Object lopId = getVertexId("software", "name", "lop");
+
+        PathsRequest.Builder builder = PathsRequest.builder();
+        builder.sources().ids(markoId);
+        builder.targets().ids(rippleId);
+        builder.step().direction(Direction.BOTH);
+        builder.maxDepth(3);
+        builder.withVertex(true);
+        PathsRequest request = builder.build();
+
+        PathsWithVertices pathsWithVertices = pathsAPI.post(request);
+
+        List<PathsWithVertices.Paths> paths = pathsWithVertices.paths();
+        Assert.assertEquals(2, paths.size());
+        List<Object> expectedIds = ImmutableList.of(markoId, lopId,
+                                                    joshId, rippleId);
+        List<List<Object>> expected = ImmutableList.of(
+                expectedIds,
+                ImmutableList.of(markoId, joshId, rippleId)
+        );
+        for (PathsWithVertices.Paths path : paths) {
+            Assert.assertTrue(expected.contains(path.objects()));
+        }
+
+        Set<Vertex> vertices = pathsWithVertices.vertices();
+        Assert.assertEquals(4, vertices.size());
+        for (Vertex v : vertices) {
+            Assert.assertTrue(expectedIds.contains(v.id()));
+        }
+    }
+
+    @Test
+    public void testPathsPostWithLabel() {
+        Object markoId = getVertexId("person", "name", "marko");
+        Object rippleId = getVertexId("software", "name", "ripple");
+        Object joshId = getVertexId("person", "name", "josh");
+        Object lopId = getVertexId("software", "name", "lop");
+
+        PathsRequest.Builder builder = PathsRequest.builder();
+        builder.sources().ids(markoId);
+        builder.targets().ids(rippleId);
+        builder.step().direction(Direction.BOTH);
+        builder.maxDepth(3);
+        PathsRequest request = builder.build();
+
+        PathsWithVertices pathsWithVertices = pathsAPI.post(request);
+
+        List<PathsWithVertices.Paths> paths = pathsWithVertices.paths();
+        Assert.assertEquals(2, paths.size());
+        List<List<Object>> expected = ImmutableList.of(
+                ImmutableList.of(markoId, lopId, joshId, rippleId),
+                ImmutableList.of(markoId, joshId, rippleId)
+        );
+        for (PathsWithVertices.Paths path : paths) {
+            Assert.assertTrue(expected.contains(path.objects()));
+        }
+
+        builder = PathsRequest.builder();
+        builder.sources().ids(markoId);
+        builder.targets().ids(rippleId);
+        builder.step().direction(Direction.BOTH).labels("created");
+        builder.maxDepth(3);
+        request = builder.build();
+        pathsWithVertices = pathsAPI.post(request);
+        paths = pathsWithVertices.paths();
+        Assert.assertEquals(1, paths.size());
+        expected = ImmutableList.of(
+                ImmutableList.of(markoId, lopId, joshId, rippleId)
+        );
+        for (PathsWithVertices.Paths path : paths) {
+            Assert.assertTrue(expected.contains(path.objects()));
+        }
+    }
+
+    @Test
+    public void testPathsPostWithNearest() {
+        Object markoId = getVertexId("person", "name", "marko");
+        Object rippleId = getVertexId("software", "name", "ripple");
+        Object joshId = getVertexId("person", "name", "josh");
+        Object lopId = getVertexId("software", "name", "lop");
+
+
+        Vertex tom = graph().addVertex(T.label, "person", "name", "Tom",
+                                       "age", 29, "city", "Shanghai");
+        Vertex jim = graph().addVertex(T.label, "person", "name", "Jim",
+                                       "age", 29, "city", "Shanghai");
+        Vertex java = graph().addVertex(T.label, "software", "name", "java",
+                                        "lang", "java", "price", 199);
+        Object tomId = tom.id();
+        Object jimId = jim.id();
+        Object javaId = java.id();
+        graph().addEdge(tomId, "created", rippleId,
+                        "date", "2016-01-10", "city", "Beijing");
+        graph().addEdge(tomId, "created", javaId,
+                        "date", "2017-01-10", "city", "Hongkong");
+        graph().addEdge(jimId, "created", javaId,
+                        "date", "2017-01-10", "city", "Hongkong");
+
+        PathsRequest.Builder builder = PathsRequest.builder();
+        builder.sources().ids(markoId);
+        builder.targets().ids(jimId);
+        builder.step().direction(Direction.BOTH);
+        builder.maxDepth(6);
+        PathsRequest request = builder.build();
+
+        PathsWithVertices pathsWithVertices = pathsAPI.post(request);
+
+        List<PathsWithVertices.Paths> paths = pathsWithVertices.paths();
+        Assert.assertEquals(2, paths.size());
+        List<List<Object>> expected = ImmutableList.of(
+                ImmutableList.of(markoId, lopId, joshId,
+                                 rippleId, tomId, javaId, jimId),
+                ImmutableList.of(markoId, joshId, rippleId,
+                                 tomId, javaId, jimId)
+        );
+        for (PathsWithVertices.Paths path : paths) {
+            Assert.assertTrue(expected.contains(path.objects()));
+        }
+
+        builder = PathsRequest.builder();
+        builder.sources().ids(markoId);
+        builder.targets().ids(jimId);
+        builder.step().direction(Direction.BOTH);
+        builder.nearest(true);
+        builder.maxDepth(6);
+        request = builder.build();
+        pathsWithVertices = pathsAPI.post(request);
+        paths = pathsWithVertices.paths();
+        Assert.assertEquals(1, paths.size());
+        expected = ImmutableList.of(
+                ImmutableList.of(markoId, joshId, rippleId,
+                                 tomId, javaId, jimId)
+        );
+        for (PathsWithVertices.Paths path : paths) {
+            Assert.assertTrue(expected.contains(path.objects()));
+        }
+    }
+
+    @Test
+    public void testPathsPostWithProperties() {
+        Object markoId = getVertexId("person", "name", "marko");
+        Object rippleId = getVertexId("software", "name", "ripple");
+        Object joshId = getVertexId("person", "name", "josh");
+        Object lopId = getVertexId("software", "name", "lop");
+
+        PathsRequest.Builder builder = PathsRequest.builder();
+        builder.sources().ids(markoId);
+        builder.targets().ids(rippleId);
+        builder.step().direction(Direction.BOTH);
+        builder.maxDepth(3);
+        PathsRequest request = builder.build();
+
+        PathsWithVertices pathsWithVertices = pathsAPI.post(request);
+
+        List<PathsWithVertices.Paths> paths = pathsWithVertices.paths();
+        Assert.assertEquals(2, paths.size());
+        List<List<Object>> expected = ImmutableList.of(
+                ImmutableList.of(markoId, lopId, joshId, rippleId),
+                ImmutableList.of(markoId, joshId, rippleId)
+        );
+        for (PathsWithVertices.Paths path : paths) {
+            Assert.assertTrue(expected.contains(path.objects()));
+        }
+
+        builder = PathsRequest.builder();
+        builder.sources().ids(markoId);
+        builder.targets().ids(rippleId);
+        builder.step().direction(Direction.BOTH)
+               .properties("date", "P.gt(\"2014-01-01 00:00:00\")");
+        builder.maxDepth(3);
+        request = builder.build();
+        pathsWithVertices = pathsAPI.post(request);
+        paths = pathsWithVertices.paths();
+        Assert.assertEquals(1, paths.size());
+        expected = ImmutableList.of(
+                ImmutableList.of(markoId, lopId, joshId, rippleId)
+        );
+        for (PathsWithVertices.Paths path : paths) {
+            Assert.assertTrue(expected.contains(path.objects()));
+        }
+    }
+
+    @Test
+    public void testPathsWithLimit() {
+        Object markoId = getVertexId("person", "name", "marko");
+        Object rippleId = getVertexId("software", "name", "ripple");
+        Object joshId = getVertexId("person", "name", "josh");
+        Object lopId = getVertexId("software", "name", "lop");
+
+        PathsRequest.Builder builder = PathsRequest.builder();
+        builder.sources().ids(markoId);
+        builder.targets().ids(rippleId);
+        builder.step().direction(Direction.BOTH);
+        builder.maxDepth(3);
+        PathsRequest request = builder.build();
+
+        PathsWithVertices pathsWithVertices = pathsAPI.post(request);
+
+        List<PathsWithVertices.Paths> paths = pathsWithVertices.paths();
+        Assert.assertEquals(2, paths.size());
+        List<List<Object>> expected = ImmutableList.of(
+                ImmutableList.of(markoId, lopId, joshId, rippleId),
+                ImmutableList.of(markoId, joshId, rippleId)
+        );
+        for (PathsWithVertices.Paths path : paths) {
+            Assert.assertTrue(expected.contains(path.objects()));
+        }
+
+        builder = PathsRequest.builder();
+        builder.sources().ids(markoId);
+        builder.targets().ids(rippleId);
+        builder.step().direction(Direction.BOTH);
+        builder.limit(1);
+        builder.maxDepth(3);
+        request = builder.build();
+        pathsWithVertices = pathsAPI.post(request);
+        paths = pathsWithVertices.paths();
+        Assert.assertEquals(1, paths.size());
+        expected = ImmutableList.of(
+                ImmutableList.of(markoId, joshId, rippleId)
+        );
+        for (PathsWithVertices.Paths path : paths) {
+            Assert.assertTrue(expected.contains(path.objects()));
+        }
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/api/traverser/PersonalRankApiTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/traverser/PersonalRankApiTest.java
new file mode 100644
index 0000000..c9596e8
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/traverser/PersonalRankApiTest.java
@@ -0,0 +1,324 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.traverser;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.baidu.hugegraph.api.BaseApiTest;
+import com.baidu.hugegraph.driver.GraphManager;
+import com.baidu.hugegraph.driver.SchemaManager;
+import com.baidu.hugegraph.structure.constant.T;
+import com.baidu.hugegraph.structure.graph.Vertex;
+import com.baidu.hugegraph.testutil.Assert;
+import com.google.common.collect.ImmutableMap;
+
+public class PersonalRankApiTest extends TraverserApiTest {
+
+    @BeforeClass
+    public static void initPersonalRankGraph() {
+        GraphManager graph = graph();
+        SchemaManager schema = schema();
+
+        schema.propertyKey("name").asText().ifNotExist().create();
+
+        schema.vertexLabel("person")
+              .properties("name")
+              .useCustomizeStringId()
+              .ifNotExist()
+              .create();
+
+        schema.vertexLabel("movie")
+              .properties("name")
+              .useCustomizeStringId()
+              .ifNotExist()
+              .create();
+
+        schema.edgeLabel("like")
+              .sourceLabel("person")
+              .targetLabel("movie")
+              .ifNotExist()
+              .create();
+
+        Vertex A = graph.addVertex(T.label, "person", T.id, "A", "name", "A");
+        Vertex B = graph.addVertex(T.label, "person", T.id, "B", "name", "B");
+        Vertex C = graph.addVertex(T.label, "person", T.id, "C", "name", "C");
+
+        Vertex a = graph.addVertex(T.label, "movie", T.id, "a", "name", "a");
+        Vertex b = graph.addVertex(T.label, "movie", T.id, "b", "name", "b");
+        Vertex c = graph.addVertex(T.label, "movie", T.id, "c", "name", "c");
+        Vertex d = graph.addVertex(T.label, "movie", T.id, "d", "name", "d");
+
+        A.addEdge("like", a);
+        A.addEdge("like", c);
+
+        B.addEdge("like", a);
+        B.addEdge("like", b);
+        B.addEdge("like", c);
+        B.addEdge("like", d);
+
+        C.addEdge("like", c);
+        C.addEdge("like", d);
+    }
+
+    @AfterClass
+    public static void clearPersonalRankGraph() {
+        List<Long> taskIds = new ArrayList<>();
+        taskIds.add(edgeLabelAPI.delete("like"));
+        taskIds.forEach(BaseApiTest::waitUntilTaskCompleted);
+        taskIds.clear();
+        taskIds.add(vertexLabelAPI.delete("movie"));
+        taskIds.add(vertexLabelAPI.delete("person"));
+        taskIds.forEach(BaseApiTest::waitUntilTaskCompleted);
+    }
+
+    @Test
+    public void testPersonalRank() {
+        PersonalRankAPI.Request.Builder builder;
+        builder = PersonalRankAPI.Request.builder();
+        builder.source("A").label("like").alpha(0.9).maxDepth(50);
+        PersonalRankAPI.Request request = builder.build();
+
+        Map<Object, Double> ranks = personalRankAPI.post(request);
+        Map<Object, Double> expectedRanks = ImmutableMap.of(
+                "B", 0.2065750574989044D,
+                "C", 0.09839507219265439D,
+                "d", 0.08959757100230095D,
+                "b", 0.04589958822642998D
+        );
+        assertDoublesEquals(expectedRanks, ranks);
+    }
+
+    @Test
+    public void testPersonalRankWithWithLabel() {
+        PersonalRankAPI.Request.Builder builder;
+        builder = PersonalRankAPI.Request.builder();
+        builder.source("A").label("like").alpha(0.9).maxDepth(50)
+               .withLabel(PersonalRankAPI.Request.WithLabel.SAME_LABEL);
+        PersonalRankAPI.Request request = builder.build();
+
+        Map<Object, Double> ranks = personalRankAPI.post(request);
+        Map<Object, Double> expectedRanks = ImmutableMap.of(
+                "B", 0.2065750574989044D,
+                "C", 0.09839507219265439D
+        );
+        assertDoublesEquals(expectedRanks, ranks);
+
+        builder = PersonalRankAPI.Request.builder();
+        builder.source("A").label("like").alpha(0.9).maxDepth(50)
+               .withLabel(PersonalRankAPI.Request.WithLabel.OTHER_LABEL);
+        request = builder.build();
+
+        ranks = personalRankAPI.post(request);
+        expectedRanks = ImmutableMap.of(
+                "d", 0.08959757100230095D,
+                "b", 0.04589958822642998D
+        );
+        assertDoublesEquals(expectedRanks, ranks);
+    }
+
+    @Test
+    public void testPersonalRankWithOtherAlpha() {
+        PersonalRankAPI.Request.Builder builder;
+        builder = PersonalRankAPI.Request.builder();
+        builder.source("A").label("like").alpha(1).maxDepth(50);
+        PersonalRankAPI.Request request = builder.build();
+
+        Map<Object, Double> ranks = personalRankAPI.post(request);
+        Map<Object, Double> expectedRanks = ImmutableMap.of(
+                "B", 0.5D,
+                "C", 0.24999999999999956D,
+                "b", 0.0D,
+                "d", 0.0D
+        );
+        assertDoublesEquals(expectedRanks, ranks);
+    }
+
+    @Test
+    public void testPersonalRankWithDegree() {
+        PersonalRankAPI.Request.Builder builder;
+        builder = PersonalRankAPI.Request.builder();
+
+        builder.source("A").label("like").alpha(0.9).degree(1).maxDepth(2);
+        PersonalRankAPI.Request request = builder.build();
+
+        // Removed root and direct neighbors of root
+        Map<Object, Double> ranks = personalRankAPI.post(request);
+        assertDoublesEquals(ImmutableMap.of(), ranks);
+
+        builder.source("A").label("like").alpha(0.9).degree(1).maxDepth(3);
+        request = builder.build();
+
+        ranks = personalRankAPI.post(request);
+        assertDoublesEquals(ImmutableMap.of(), ranks);
+
+        builder.source("A").label("like").alpha(0.9).degree(2).maxDepth(2);
+        request = builder.build();
+
+        ranks = personalRankAPI.post(request);
+        assertDoublesEquals(ImmutableMap.of("B", 0.405D), ranks);
+
+        builder.source("A").label("like").alpha(0.9).degree(2).maxDepth(3);
+        request = builder.build();
+
+        ranks = personalRankAPI.post(request);
+        Assert.assertEquals(2, ranks.size());
+    }
+
+    @Test
+    public void testPersonalRankWithLimit() {
+        PersonalRankAPI.Request.Builder builder;
+        builder = PersonalRankAPI.Request.builder();
+        builder.source("A").label("like").alpha(0.9).limit(3).maxDepth(50);
+        PersonalRankAPI.Request request = builder.build();
+
+        Map<Object, Double> ranks = personalRankAPI.post(request);
+        Map<Object, Double> expectedRanks = ImmutableMap.of(
+                "B", 0.2065750574989044D,
+                "C", 0.09839507219265439D,
+                "d", 0.08959757100230095D
+        );
+        assertDoublesEquals(expectedRanks, ranks);
+    }
+
+    @Test
+    public void testPersonalRankWithMaxDepth() {
+        PersonalRankAPI.Request.Builder builder;
+        builder = PersonalRankAPI.Request.builder();
+        builder.source("A").label("like").alpha(0.9).maxDepth(20);
+        PersonalRankAPI.Request request = builder.build();
+
+        Map<Object, Double> ranks = personalRankAPI.post(request);
+        Map<Object, Double> expectedRanks = ImmutableMap.of(
+                "B", 0.23414889646372697D,
+                "C", 0.11218194186115384D,
+                "d", 0.07581065434649958D,
+                "b", 0.03900612828909826D
+        );
+        assertDoublesEquals(expectedRanks, ranks);
+    }
+
+    @Test
+    public void testPersonalRankWithUnsorted() {
+        PersonalRankAPI.Request.Builder builder;
+        builder = PersonalRankAPI.Request.builder();
+        builder.source("A").label("like").alpha(0.9).maxDepth(50).sorted(false);
+        PersonalRankAPI.Request request = builder.build();
+
+        Map<Object, Double> ranks = personalRankAPI.post(request);
+        Map<Object, Double> expectedRanks = ImmutableMap.of(
+                "b", 0.04589958822642998D,
+                "B", 0.2065750574989044D,
+                "C", 0.09839507219265439D,
+                "d", 0.08959757100230095D
+        );
+        assertDoublesEquals(expectedRanks, ranks);
+    }
+
+    @Test
+    public void testPersonalRankWithIsolatedVertex() {
+        Vertex isolate = graph().addVertex(T.label, "person", T.id, "isolate",
+                                           "name", "isolate-vertex");
+
+        PersonalRankAPI.Request.Builder builder;
+        builder = PersonalRankAPI.Request.builder();
+        builder.source("isolate").label("like").alpha(0.9).maxDepth(50);
+        PersonalRankAPI.Request request = builder.build();
+
+        Map<Object, Double> ranks = personalRankAPI.post(request);
+        assertDoublesEquals(ImmutableMap.of(), ranks);
+
+        graph().removeVertex(isolate.id());
+    }
+
+    @Test
+    public void testPersonalRankWithInvalidParams() {
+        // Invalid source
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            PersonalRankAPI.Request.Builder builder;
+            builder = PersonalRankAPI.Request.builder();
+            builder.source(null);
+        });
+
+        // Invalid label
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            PersonalRankAPI.Request.Builder builder;
+            builder = PersonalRankAPI.Request.builder();
+            builder.label(null);
+        });
+
+        // Invalid alpha
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            PersonalRankAPI.Request.Builder builder;
+            builder = PersonalRankAPI.Request.builder();
+            builder.alpha(0.0);
+        });
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            PersonalRankAPI.Request.Builder builder;
+            builder = PersonalRankAPI.Request.builder();
+            builder.alpha(1.1);
+        });
+
+        // Invalid degree
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            PersonalRankAPI.Request.Builder builder;
+            builder = PersonalRankAPI.Request.builder();
+            builder.degree(-2);
+        });
+
+        // Invalid limit
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            PersonalRankAPI.Request.Builder builder;
+            builder = PersonalRankAPI.Request.builder();
+            builder.limit(-2);
+        });
+
+        // Invalid maxDepth
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            PersonalRankAPI.Request.Builder builder;
+            builder = PersonalRankAPI.Request.builder();
+            builder.maxDepth(0);
+        });
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            PersonalRankAPI.Request.Builder builder;
+            builder = PersonalRankAPI.Request.builder();
+            builder.maxDepth(10000);
+        });
+    }
+
+    private static void assertDoublesEquals(Map<Object, Double> expects,
+                                            Map<Object, Double> actuals) {
+        Assert.assertEquals(expects.size(), actuals.size());
+        Assert.assertTrue(expects.keySet().containsAll(actuals.keySet()));
+        for (Object expectKey : expects.keySet()) {
+            Double expectValue = expects.get(expectKey);
+            Double actualValue = actuals.get(expectKey);
+            Assert.assertTrue(String.format("expected %s, actual %s",
+                                            expectValue, actualValue),
+                              Math.abs(expectValue - actualValue) <
+                              Math.pow(1, -10));
+        }
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/api/traverser/RingsRaysApiTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/traverser/RingsRaysApiTest.java
new file mode 100644
index 0000000..20b86fa
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/traverser/RingsRaysApiTest.java
@@ -0,0 +1,327 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.traverser;
+
+import java.util.List;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.baidu.hugegraph.api.BaseApiTest;
+import com.baidu.hugegraph.exception.ServerException;
+import com.baidu.hugegraph.structure.constant.Direction;
+import com.baidu.hugegraph.structure.constant.T;
+import com.baidu.hugegraph.structure.graph.Path;
+import com.baidu.hugegraph.structure.graph.Vertex;
+import com.baidu.hugegraph.testutil.Assert;
+import com.google.common.collect.ImmutableList;
+
+public class RingsRaysApiTest extends TraverserApiTest {
+
+    @BeforeClass
+    public static void prepareSchemaAndGraph() {
+        BaseApiTest.initPropertyKey();
+        BaseApiTest.initVertexLabel();
+        BaseApiTest.initEdgeLabel();
+        BaseApiTest.initIndexLabel();
+        BaseApiTest.initVertex();
+        BaseApiTest.initEdge();
+
+        schema().vertexLabel("node")
+                .useCustomizeNumberId()
+                .ifNotExist()
+                .create();
+
+        schema().edgeLabel("link")
+                .sourceLabel("node").targetLabel("node")
+                .ifNotExist()
+                .create();
+
+        Vertex v1 = graph().addVertex(T.label, "node", T.id, 1);
+        Vertex v2 = graph().addVertex(T.label, "node", T.id, 2);
+        Vertex v3 = graph().addVertex(T.label, "node", T.id, 3);
+
+        // Path length 5
+        v1.addEdge("link", v2);
+        v2.addEdge("link", v3);
+        v3.addEdge("link", v1);
+        v3.addEdge("link", v2);
+    }
+
+    @Test
+    public void testRings() {
+        Object markoId = getVertexId("person", "name", "marko");
+        List<Path> paths = ringsAPI.get(markoId, Direction.BOTH, null,
+                                        2, false, -1L, -1L, -1L);
+        Assert.assertEquals(0, paths.size());
+    }
+
+    @Test
+    public void testRingsWithLimit() {
+        Object markoId = getVertexId("person", "name", "marko");
+        Object joshId = getVertexId("person", "name", "josh");
+        Object lopId = getVertexId("software", "name", "lop");
+
+        List<Path> paths = ringsAPI.get(markoId, Direction.BOTH, null,
+                                        3, false, -1L, -1L, 1L);
+        Assert.assertEquals(1, paths.size());
+        List<Object> path1 = ImmutableList.of(markoId, joshId, lopId, markoId);
+        List<Object> path2 = ImmutableList.of(markoId, lopId, joshId, markoId);
+        List<List<Object>> expectedPaths = ImmutableList.of(path1, path2);
+        Assert.assertTrue(expectedPaths.contains(paths.get(0).objects()));
+    }
+
+    @Test
+    public void testRingsWithDepth() {
+        Object markoId = getVertexId("person", "name", "marko");
+        Object joshId = getVertexId("person", "name", "josh");
+        Object lopId = getVertexId("software", "name", "lop");
+
+        List<Path> paths = ringsAPI.get(markoId, Direction.BOTH, null,
+                                        1, false, -1L, -1L, -1L);
+        Assert.assertEquals(0, paths.size());
+
+        paths = ringsAPI.get(markoId, Direction.BOTH, null,
+                             2, false, -1L, -1L, -1L);
+        Assert.assertEquals(0, paths.size());
+
+        paths = ringsAPI.get(markoId, Direction.BOTH, null,
+                             3, false, -1L, -1L, -1L);
+        Assert.assertEquals(1, paths.size());
+        List<Object> path1 = ImmutableList.of(markoId, joshId, lopId, markoId);
+        List<Object> path2 = ImmutableList.of(markoId, lopId, joshId, markoId);
+        List<List<Object>> expectedPaths = ImmutableList.of(path1, path2);
+        Assert.assertTrue(expectedPaths.contains(paths.get(0).objects()));
+    }
+
+    @Test
+    public void testRingsWithCapacity() {
+        Object markoId = getVertexId("person", "name", "marko");
+
+        Assert.assertThrows(ServerException.class, () -> {
+            ringsAPI.get(markoId, Direction.BOTH, null,
+                         2, false, -1L, 1L, -1L);
+        }, e -> {
+            String expect = "Exceed capacity '1' while finding rings";
+            Assert.assertContains(expect, e.getMessage());
+        });
+    }
+
+    @Test
+    public void testRingsSourceInRing() {
+        List<Path> paths = ringsAPI.get(1, Direction.BOTH, null,
+                                        3, true, -1L, -1L, -1L);
+        Assert.assertEquals(1, paths.size());
+        List<Object> path1 = ImmutableList.of(1, 3, 2, 1);
+        List<Object> path2 = ImmutableList.of(1, 2, 3, 1);
+        List<List<Object>> expectedPaths = ImmutableList.of(path1, path2);
+        Assert.assertTrue(expectedPaths.contains(paths.get(0).objects()));
+
+        paths = ringsAPI.get(2, Direction.OUT, null,
+                             2, true, -1L, -1L, -1L);
+        Assert.assertEquals(1, paths.size());
+        List<Object> path3 = ImmutableList.of(2, 3, 2);
+        Assert.assertEquals(path3, paths.get(0).objects());
+
+        paths = ringsAPI.get(2, Direction.BOTH, null,
+                             2, true, -1L, -1L, -1L);
+        Assert.assertEquals(1, paths.size());
+        Assert.assertEquals(path3, paths.get(0).objects());
+    }
+
+    @Test
+    public void testRingsWithoutSourceInRing() {
+        List<Path> paths = ringsAPI.get(1, Direction.BOTH, null,
+                                        3, false, -1L, -1L, -1L);
+        Assert.assertEquals(3, paths.size());
+        List<Object> path1 = ImmutableList.of(1, 3, 2, 3);
+        List<Object> path2 = ImmutableList.of(1, 3, 2, 1);
+        List<Object> path3 = ImmutableList.of(1, 2, 3, 1);
+        List<Object> path4 = ImmutableList.of(1, 2, 3, 2);
+        List<List<Object>> expectedPaths = ImmutableList.of(path1, path2,
+                                                            path3, path4);
+        Assert.assertTrue(expectedPaths.contains(paths.get(0).objects()));
+        Assert.assertTrue(expectedPaths.contains(paths.get(1).objects()));
+        Assert.assertTrue(expectedPaths.contains(paths.get(2).objects()));
+
+        paths = ringsAPI.get(2, Direction.OUT, null,
+                             3, false, -1L, -1L, -1L);
+        Assert.assertEquals(2, paths.size());
+        List<Object> path5 = ImmutableList.of(2, 3, 2);
+        List<Object> path6 = ImmutableList.of(2, 3, 1, 2);
+        expectedPaths = ImmutableList.of(path5, path6);
+        Assert.assertTrue(expectedPaths.contains(paths.get(0).objects()));
+        Assert.assertTrue(expectedPaths.contains(paths.get(1).objects()));
+
+        paths = ringsAPI.get(2, Direction.BOTH, null,
+                             3, false, -1L, -1L, -1L);
+        Assert.assertEquals(2, paths.size());
+        List<Object> path7 = ImmutableList.of(2, 3, 1, 2);
+        List<Object> path8 = ImmutableList.of(2, 1, 3, 2);
+        expectedPaths = ImmutableList.of(path5, path7, path8);
+        Assert.assertTrue(expectedPaths.contains(paths.get(0).objects()));
+        Assert.assertTrue(expectedPaths.contains(paths.get(1).objects()));
+    }
+
+    @Test
+    public void testRays() {
+        Object markoId = getVertexId("person", "name", "marko");
+        Object joshId = getVertexId("person", "name", "josh");
+        Object vadasId = getVertexId("person", "name", "vadas");
+        Object lopId = getVertexId("software", "name", "lop");
+        Object rippleId = getVertexId("software", "name", "ripple");
+
+        List<Path> paths = raysAPI.get(markoId, Direction.OUT, null,
+                                       2, -1L, -1L, -1L);
+        Assert.assertEquals(4, paths.size());
+        List<Object> path1 = ImmutableList.of(markoId, lopId);
+        List<Object> path2 = ImmutableList.of(markoId, vadasId);
+        List<Object> path3 = ImmutableList.of(markoId, joshId, rippleId);
+        List<Object> path4 = ImmutableList.of(markoId, joshId, lopId);
+        List<List<Object>> expectedPaths = ImmutableList.of(path1, path2,
+                                                            path3, path4);
+        Assert.assertTrue(expectedPaths.contains(paths.get(0).objects()));
+        Assert.assertTrue(expectedPaths.contains(paths.get(1).objects()));
+        Assert.assertTrue(expectedPaths.contains(paths.get(2).objects()));
+        Assert.assertTrue(expectedPaths.contains(paths.get(3).objects()));
+    }
+
+    @Test
+    public void testRaysWithLimit() {
+        Object markoId = getVertexId("person", "name", "marko");
+        Object vadasId = getVertexId("person", "name", "vadas");
+        Object lopId = getVertexId("software", "name", "lop");
+
+        List<Path> paths = raysAPI.get(markoId, Direction.OUT, null,
+                                       2, -1L, -1L, 2L);
+        Assert.assertEquals(2, paths.size());
+        List<Object> path1 = ImmutableList.of(markoId, lopId);
+        List<Object> path2 = ImmutableList.of(markoId, vadasId);
+        List<List<Object>> expectedPaths = ImmutableList.of(path1, path2);
+        Assert.assertTrue(expectedPaths.contains(paths.get(0).objects()));
+        Assert.assertTrue(expectedPaths.contains(paths.get(1).objects()));
+    }
+
+    @Test
+    public void testRaysWithDepth() {
+        Object markoId = getVertexId("person", "name", "marko");
+        Object joshId = getVertexId("person", "name", "josh");
+        Object vadasId = getVertexId("person", "name", "vadas");
+        Object lopId = getVertexId("software", "name", "lop");
+        Object rippleId = getVertexId("software", "name", "ripple");
+
+        List<Path> paths = raysAPI.get(markoId, Direction.OUT, null,
+                                       1, -1L, -1L, -1L);
+        Assert.assertEquals(3, paths.size());
+        List<Object> path1 = ImmutableList.of(markoId, lopId);
+        List<Object> path2 = ImmutableList.of(markoId, vadasId);
+        List<Object> path3 = ImmutableList.of(markoId, joshId);
+        List<List<Object>> expectedPaths = ImmutableList.of(path1, path2,
+                                                            path3);
+        Assert.assertTrue(expectedPaths.contains(paths.get(0).objects()));
+        Assert.assertTrue(expectedPaths.contains(paths.get(1).objects()));
+        Assert.assertTrue(expectedPaths.contains(paths.get(2).objects()));
+
+        paths = raysAPI.get(markoId, Direction.OUT, null,
+                            2, -1L, -1L, -1L);
+        Assert.assertEquals(4, paths.size());
+        List<Object> path4 = ImmutableList.of(markoId, joshId, rippleId);
+        List<Object> path5 = ImmutableList.of(markoId, joshId, lopId);
+        expectedPaths = ImmutableList.of(path1, path2, path4, path5);
+        Assert.assertTrue(expectedPaths.contains(paths.get(0).objects()));
+        Assert.assertTrue(expectedPaths.contains(paths.get(1).objects()));
+        Assert.assertTrue(expectedPaths.contains(paths.get(2).objects()));
+        Assert.assertTrue(expectedPaths.contains(paths.get(3).objects()));
+
+        paths = raysAPI.get(markoId, Direction.OUT, null,
+                            3, -1L, -1L, -1L);
+        Assert.assertEquals(4, paths.size());
+        Assert.assertTrue(expectedPaths.contains(paths.get(0).objects()));
+        Assert.assertTrue(expectedPaths.contains(paths.get(1).objects()));
+        Assert.assertTrue(expectedPaths.contains(paths.get(2).objects()));
+        Assert.assertTrue(expectedPaths.contains(paths.get(3).objects()));
+    }
+
+    @Test
+    public void testRaysWithCapacity() {
+        Object markoId = getVertexId("person", "name", "marko");
+
+        Assert.assertThrows(ServerException.class, () -> {
+            raysAPI.get(markoId, Direction.OUT, null,
+                        2, -1L, 1L, -1L);
+        }, e -> {
+            String expect = "Exceed capacity '1' while finding rays";
+            Assert.assertContains(expect, e.getMessage());
+        });
+    }
+
+    @Test
+    public void testRaysWithBoth() {
+        Object markoId = getVertexId("person", "name", "marko");
+        Object joshId = getVertexId("person", "name", "josh");
+        Object vadasId = getVertexId("person", "name", "vadas");
+        Object peterId = getVertexId("person", "name", "peter");
+        Object lopId = getVertexId("software", "name", "lop");
+        Object rippleId = getVertexId("software", "name", "ripple");
+
+        List<Path> paths = raysAPI.get(markoId, Direction.BOTH, null,
+                                       1, -1L, -1L, -1L);
+        Assert.assertEquals(3, paths.size());
+        List<Object> path1 = ImmutableList.of(markoId, lopId);
+        List<Object> path2 = ImmutableList.of(markoId, vadasId);
+        List<Object> path3 = ImmutableList.of(markoId, joshId);
+        List<List<Object>> expectedPaths = ImmutableList.of(path1, path2,
+                                                            path3);
+        Assert.assertTrue(expectedPaths.contains(paths.get(0).objects()));
+        Assert.assertTrue(expectedPaths.contains(paths.get(1).objects()));
+        Assert.assertTrue(expectedPaths.contains(paths.get(2).objects()));
+
+        paths = raysAPI.get(markoId, Direction.BOTH, null,
+                            2, -1L, -1L, -1L);
+        Assert.assertEquals(5, paths.size());
+        List<Object> path4 = ImmutableList.of(markoId, vadasId);
+        List<Object> path5 = ImmutableList.of(markoId, lopId, joshId);
+        List<Object> path6 = ImmutableList.of(markoId, lopId, peterId);
+        List<Object> path7 = ImmutableList.of(markoId, joshId, lopId);
+        List<Object> path8 = ImmutableList.of(markoId, joshId, rippleId);
+        expectedPaths = ImmutableList.of(path4, path5, path6, path7, path8);
+        Assert.assertTrue(expectedPaths.contains(paths.get(0).objects()));
+        Assert.assertTrue(expectedPaths.contains(paths.get(1).objects()));
+        Assert.assertTrue(expectedPaths.contains(paths.get(2).objects()));
+        Assert.assertTrue(expectedPaths.contains(paths.get(3).objects()));
+        Assert.assertTrue(expectedPaths.contains(paths.get(4).objects()));
+
+        paths = raysAPI.get(markoId, Direction.BOTH, null,
+                            3, -1L, -1L, -1L);
+        Assert.assertEquals(5, paths.size());
+        List<Object> path9 = ImmutableList.of(markoId, vadasId);
+        List<Object> path10 = ImmutableList.of(markoId, joshId, rippleId);
+        List<Object> path11 = ImmutableList.of(markoId, lopId, peterId);
+        List<Object> path12 = ImmutableList.of(markoId, joshId, lopId, peterId);
+        List<Object> path13 = ImmutableList.of(markoId, lopId,
+                                               joshId, rippleId);
+        expectedPaths = ImmutableList.of(path9, path10, path11, path12, path13);
+        Assert.assertTrue(expectedPaths.contains(paths.get(0).objects()));
+        Assert.assertTrue(expectedPaths.contains(paths.get(1).objects()));
+        Assert.assertTrue(expectedPaths.contains(paths.get(2).objects()));
+        Assert.assertTrue(expectedPaths.contains(paths.get(3).objects()));
+        Assert.assertTrue(expectedPaths.contains(paths.get(4).objects()));
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/api/traverser/SameNeighborsApiTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/traverser/SameNeighborsApiTest.java
new file mode 100644
index 0000000..0b0275d
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/traverser/SameNeighborsApiTest.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.traverser;
+
+import java.util.List;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.baidu.hugegraph.structure.constant.Direction;
+import com.baidu.hugegraph.structure.constant.T;
+import com.baidu.hugegraph.structure.graph.Vertex;
+import com.baidu.hugegraph.testutil.Assert;
+import com.google.common.collect.ImmutableList;
+
+public class SameNeighborsApiTest extends TraverserApiTest {
+
+    @BeforeClass
+    public static void initShortestPathGraph() {
+        schema().vertexLabel("node")
+                .useCustomizeNumberId()
+                .ifNotExist()
+                .create();
+
+        schema().edgeLabel("link")
+                .sourceLabel("node").targetLabel("node")
+                .ifNotExist()
+                .create();
+        schema().edgeLabel("relateTo")
+                .sourceLabel("node").targetLabel("node")
+                .ifNotExist()
+                .create();
+
+        Vertex v1 = graph().addVertex(T.label, "node", T.id, 1);
+        Vertex v2 = graph().addVertex(T.label, "node", T.id, 2);
+        Vertex v3 = graph().addVertex(T.label, "node", T.id, 3);
+        Vertex v4 = graph().addVertex(T.label, "node", T.id, 4);
+        Vertex v5 = graph().addVertex(T.label, "node", T.id, 5);
+        Vertex v6 = graph().addVertex(T.label, "node", T.id, 6);
+        Vertex v7 = graph().addVertex(T.label, "node", T.id, 7);
+        Vertex v8 = graph().addVertex(T.label, "node", T.id, 8);
+        Vertex v9 = graph().addVertex(T.label, "node", T.id, 9);
+        Vertex v10 = graph().addVertex(T.label, "node", T.id, 10);
+
+        v1.addEdge("link", v3);
+        v2.addEdge("link", v3);
+        v4.addEdge("link", v1);
+        v4.addEdge("link", v2);
+
+        v1.addEdge("relateTo", v5);
+        v2.addEdge("relateTo", v5);
+        v6.addEdge("relateTo", v1);
+        v6.addEdge("relateTo", v2);
+
+        v1.addEdge("link", v7);
+        v8.addEdge("link", v1);
+        v2.addEdge("link", v9);
+        v10.addEdge("link", v2);
+    }
+
+    @Test
+    public void testSameNeighbors() {
+        List<Object> neighbors = sameNeighborsAPI.get(1, 2, Direction.BOTH,
+                                                      null, -1, -1L);
+        Assert.assertEquals(4, neighbors.size());
+
+        Assert.assertTrue(neighbors.containsAll(ImmutableList.of(3, 4, 5, 6)));
+    }
+
+    @Test
+    public void testSameNeighborsWithDirection() {
+        List<Object> neighbors = sameNeighborsAPI.get(1, 2, Direction.OUT,
+                                                      null, -1, -1L);
+        Assert.assertEquals(2, neighbors.size());
+
+        Assert.assertTrue(neighbors.containsAll(ImmutableList.of(3, 5)));
+
+        neighbors = sameNeighborsAPI.get(1, 2, Direction.IN,
+                                         null, -1, -1L);
+        Assert.assertEquals(2, neighbors.size());
+
+        Assert.assertTrue(neighbors.containsAll(ImmutableList.of(4, 6)));
+    }
+
+    @Test
+    public void testSameNeighborsWithLabel() {
+        List<Object> neighbors = sameNeighborsAPI.get(1, 2, Direction.BOTH,
+                                                      "link", -1, -1L);
+        Assert.assertEquals(2, neighbors.size());
+
+        Assert.assertTrue(neighbors.containsAll(ImmutableList.of(3, 4)));
+
+        neighbors = sameNeighborsAPI.get(1, 2, Direction.OUT,
+                                         "link", -1, -1L);
+        Assert.assertEquals(1, neighbors.size());
+
+        Assert.assertTrue(neighbors.contains(3));
+
+        neighbors = sameNeighborsAPI.get(1, 2, Direction.IN,
+                                         "link", -1, -1L);
+        Assert.assertEquals(1, neighbors.size());
+
+        Assert.assertTrue(neighbors.contains(4));
+
+        neighbors = sameNeighborsAPI.get(1, 2, Direction.BOTH,
+                                         "relateTo", -1, -1L);
+        Assert.assertEquals(2, neighbors.size());
+
+        Assert.assertTrue(neighbors.containsAll(ImmutableList.of(5, 6)));
+
+        neighbors = sameNeighborsAPI.get(1, 2, Direction.OUT,
+                                         "relateTo", -1, -1L);
+        Assert.assertEquals(1, neighbors.size());
+
+        Assert.assertTrue(neighbors.contains(5));
+
+        neighbors = sameNeighborsAPI.get(1, 2, Direction.IN,
+                                         "relateTo", -1, -1L);
+        Assert.assertEquals(1, neighbors.size());
+
+        Assert.assertTrue(neighbors.contains(6));
+    }
+
+    @Test
+    public void testSameNeighborsWithDegree() {
+        List<Object> neighbors = sameNeighborsAPI.get(1, 2, Direction.OUT,
+                                                      null, 6, -1L);
+        Assert.assertEquals(2, neighbors.size());
+
+        Assert.assertTrue(neighbors.containsAll(ImmutableList.of(3, 5)));
+
+        neighbors = sameNeighborsAPI.get(1, 2, Direction.OUT,
+                                         null, 1, -1L);
+        Assert.assertEquals(1, neighbors.size());
+
+        Assert.assertTrue(neighbors.contains(3));
+    }
+
+    @Test
+    public void testSameNeighborsWithLimit() {
+        List<Object> neighbors = sameNeighborsAPI.get(1, 2, Direction.BOTH,
+                                                      "link", 6, -1L);
+        Assert.assertEquals(2, neighbors.size());
+
+        Assert.assertTrue(neighbors.containsAll(ImmutableList.of(3, 4)));
+
+        neighbors = sameNeighborsAPI.get(1, 2, Direction.BOTH,
+                                         "link", 6, 2L);
+        Assert.assertEquals(2, neighbors.size());
+
+        Assert.assertTrue(neighbors.containsAll(ImmutableList.of(3, 4)));
+
+        neighbors = sameNeighborsAPI.get(1, 2, Direction.BOTH,
+                                         "link", 6, 1L);
+        Assert.assertEquals(1, neighbors.size());
+
+        Assert.assertTrue(neighbors.contains(3));
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/api/traverser/ShortestPathApiTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/traverser/ShortestPathApiTest.java
new file mode 100644
index 0000000..8c07e5a
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/traverser/ShortestPathApiTest.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.traverser;
+
+import java.util.List;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.baidu.hugegraph.exception.ServerException;
+import com.baidu.hugegraph.structure.constant.Direction;
+import com.baidu.hugegraph.structure.constant.T;
+import com.baidu.hugegraph.structure.graph.Path;
+import com.baidu.hugegraph.structure.graph.Vertex;
+import com.baidu.hugegraph.testutil.Assert;
+import com.google.common.collect.ImmutableList;
+
+public class ShortestPathApiTest extends TraverserApiTest {
+
+    @BeforeClass
+    public static void initShortestPathGraph() {
+        schema().vertexLabel("node")
+                .useCustomizeNumberId()
+                .ifNotExist()
+                .create();
+
+        schema().edgeLabel("link")
+                .sourceLabel("node").targetLabel("node")
+                .ifNotExist()
+                .create();
+
+        Vertex v1 = graph().addVertex(T.label, "node", T.id, 1);
+        Vertex v2 = graph().addVertex(T.label, "node", T.id, 2);
+        Vertex v3 = graph().addVertex(T.label, "node", T.id, 3);
+        Vertex v4 = graph().addVertex(T.label, "node", T.id, 4);
+        Vertex v5 = graph().addVertex(T.label, "node", T.id, 5);
+        Vertex v6 = graph().addVertex(T.label, "node", T.id, 6);
+        Vertex v7 = graph().addVertex(T.label, "node", T.id, 7);
+        Vertex v8 = graph().addVertex(T.label, "node", T.id, 8);
+        Vertex v9 = graph().addVertex(T.label, "node", T.id, 9);
+        Vertex v10 = graph().addVertex(T.label, "node", T.id, 10);
+        Vertex v11 = graph().addVertex(T.label, "node", T.id, 11);
+        Vertex v12 = graph().addVertex(T.label, "node", T.id, 12);
+        Vertex v13 = graph().addVertex(T.label, "node", T.id, 13);
+        Vertex v14 = graph().addVertex(T.label, "node", T.id, 14);
+        Vertex v15 = graph().addVertex(T.label, "node", T.id, 15);
+        Vertex v16 = graph().addVertex(T.label, "node", T.id, 16);
+        Vertex v17 = graph().addVertex(T.label, "node", T.id, 17);
+        Vertex v18 = graph().addVertex(T.label, "node", T.id, 18);
+
+        // Path length 5
+        v1.addEdge("link", v2);
+        v2.addEdge("link", v3);
+        v3.addEdge("link", v4);
+        v4.addEdge("link", v5);
+        v5.addEdge("link", v6);
+
+        // Path length 4
+        v1.addEdge("link", v7);
+        v7.addEdge("link", v8);
+        v8.addEdge("link", v9);
+        v9.addEdge("link", v6);
+
+        // Path length 3
+        v1.addEdge("link", v10);
+        v10.addEdge("link", v11);
+        v11.addEdge("link", v6);
+
+        // Add other 3 neighbor for v7
+        v7.addEdge("link", v12);
+        v7.addEdge("link", v13);
+        v7.addEdge("link", v14);
+
+        // Add other 4 neighbor for v10
+        v10.addEdge("link", v15);
+        v10.addEdge("link", v16);
+        v10.addEdge("link", v17);
+        v10.addEdge("link", v18);
+    }
+
+    @Test
+    public void testShortestPath() {
+        Path path = shortestPathAPI.get(1, 6, Direction.BOTH,
+                                        null, 6, -1L, 0L, -1L);
+        Assert.assertEquals(4, path.size());
+        Assert.assertEquals(ImmutableList.of(1, 10, 11, 6), path.objects());
+    }
+
+    @Test
+    public void testShortestPathWithLabel() {
+        Path path = shortestPathAPI.get(1, 6, Direction.BOTH,
+                                        "link", 6, -1L, 0L, -1L);
+        Assert.assertEquals(4, path.size());
+        Assert.assertEquals(ImmutableList.of(1, 10, 11, 6), path.objects());
+    }
+
+    @Test
+    public void testShortestPathWithDegree() {
+        Path path = shortestPathAPI.get(1, 6, Direction.OUT,
+                                        null, 6, 1L, 0L, -1L);
+        /*
+         * Following results can be guaranteed in RocksDB backend,
+         * but different results exist in table type backend(like Cassandra).
+         */
+        Assert.assertEquals(6, path.size());
+        Assert.assertEquals(ImmutableList.of(1, 2, 3, 4, 5, 6), path.objects());
+    }
+
+    @Test
+    public void testShortestPathWithCapacity() {
+        Path path = shortestPathAPI.get(14, 6, Direction.BOTH,
+                                        null, 6, 5L, 0L, 19L);
+        Assert.assertEquals(5, path.size());
+        Assert.assertEquals(ImmutableList.of(14, 7, 8, 9, 6), path.objects());
+
+        Assert.assertThrows(ServerException.class, () -> {
+            shortestPathAPI.get(14, 6, Direction.BOTH, null, 6, 1L, 0L, 2L);
+        }, e -> {
+            String expect = "Exceed capacity '2' while finding shortest path";
+            Assert.assertContains(expect, e.getMessage());
+        });
+    }
+
+    @Test
+    public void testShortestPathWithMaxDepth() {
+        Path path = shortestPathAPI.get(14, 6, Direction.BOTH,
+                                        null, 4, 5L, 0L, 19L);
+        Assert.assertEquals(5, path.size());
+        Assert.assertEquals(ImmutableList.of(14, 7, 8, 9, 6), path.objects());
+
+        path = shortestPathAPI.get(14, 6, Direction.BOTH,
+                                   null, 3, 5L, 0L, 19L);
+        Assert.assertEquals(0, path.size());
+    }
+
+    @Test
+    public void testShortestPathWithSkipDegree() {
+        // Path length 5 with min degree 3(v1 degree is 3)
+        List<Object> path1 = ImmutableList.of(1, 2, 3, 4, 5, 6);
+        // Path length 4 with middle degree 4(v7 degree is 4)
+        List<Object> path2 = ImmutableList.of(1, 7, 8, 9, 6);
+        // Path length 3 with max degree 5(v10 degree is 5)
+        List<Object> path3 = ImmutableList.of(1, 10, 11, 6);
+
+        // (skipped degree == degree) > max degree
+        Path path = shortestPathAPI.get(1, 6, Direction.OUT,
+                                        null, 5, 6L, 6L, -1L);
+        Assert.assertEquals(4, path.size());
+        Assert.assertEquals(path3, path.objects());
+
+        // (skipped degree == degree) == max degree
+        path = shortestPathAPI.get(1, 6, Direction.OUT,
+                                   null, 5, 5L, 5L, -1L);
+        Assert.assertEquals(5, path.size());
+        Assert.assertEquals(path2, path.objects());
+
+        // min degree < (skipped degree == degree) == middle degree < max degree
+        path = shortestPathAPI.get(1, 6, Direction.OUT,
+                                   null, 5, 4L, 4L, -1L);
+        Assert.assertEquals(6, path.size());
+        Assert.assertEquals(path1, path.objects());
+
+        // (skipped degree == degree) <= min degree
+        path = shortestPathAPI.get(1, 6, Direction.OUT,
+                                   null, 5, 3L, 3L, -1L);
+        Assert.assertEquals(0, path.size());
+
+        // Skipped degree > max degree, degree <= min degree
+        path = shortestPathAPI.get(1, 6, Direction.OUT,
+                                   null, 5, 3L, 6L, -1L);
+        Assert.assertTrue(path.size() == 4 ||
+                          path.size() == 5 ||
+                          path.size() == 6);
+        List<List<Object>> paths = ImmutableList.of(path1, path2, path3);
+        Assert.assertTrue(paths.contains(path.objects()));
+
+        // Skipped degree > max degree, min degree < degree < max degree
+        path = shortestPathAPI.get(1, 6, Direction.OUT,
+                                   null, 5, 4L, 6L, -1L);
+        Assert.assertTrue(path.size() == 4 || path.size() == 5);
+        Assert.assertTrue(path2.equals(path.objects()) ||
+                          path3.equals(path.objects()));
+
+        // Skipped degree > max degree, degree >= max degree
+        path = shortestPathAPI.get(1, 6, Direction.OUT,
+                                   null, 5, 5L, 6L, -1L);
+        Assert.assertEquals(4, path.size());
+        Assert.assertEquals(path3, path.objects());
+    }
+
+    @Test
+    public void testShortestPathWithIllegalArgs() {
+        // The max depth shouldn't be 0 or negative
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            shortestPathAPI.get("a", "b", Direction.BOTH,
+                                null, -1, 1L, 0L, 2L);
+        });
+
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            shortestPathAPI.get("a", "b", Direction.BOTH,
+                                null, 0, 1L, 0L, 2L);
+        });
+
+        // The degree shouldn't be 0 or negative but NO_LIMIT(-1)
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            shortestPathAPI.get("a", "b", Direction.BOTH,
+                                null, 5, 0L, 0L, 2L);
+        });
+
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            shortestPathAPI.get("a", "b", Direction.BOTH,
+                                null, 5, -3L, 0L, 2L);
+        });
+
+        // The skipped degree shouldn't be negative
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            shortestPathAPI.get("a", "b", Direction.BOTH,
+                                null, 5, 1L, -1L, 2L);
+        });
+
+        // The skipped degree shouldn't be >= capacity
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            shortestPathAPI.get("a", "b", Direction.BOTH,
+                                null, 5, 1L, 2L, 2L);
+        });
+
+        // The skipped degree shouldn't be < degree
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            shortestPathAPI.get("a", "b", Direction.BOTH,
+                                null, 5, 3L, 2L, 10L);
+        });
+
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            shortestPathAPI.get("a", "b", Direction.BOTH,
+                                null, 5, -1L, 2L, 10L);
+        });
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/api/traverser/SingleSourceShortestPathApiTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/traverser/SingleSourceShortestPathApiTest.java
new file mode 100644
index 0000000..bb92b21
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/traverser/SingleSourceShortestPathApiTest.java
@@ -0,0 +1,507 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.traverser;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.baidu.hugegraph.structure.constant.Direction;
+import com.baidu.hugegraph.structure.constant.T;
+import com.baidu.hugegraph.structure.graph.Vertex;
+import com.baidu.hugegraph.structure.traverser.WeightedPath;
+import com.baidu.hugegraph.structure.traverser.WeightedPaths;
+import com.baidu.hugegraph.testutil.Assert;
+import com.google.common.collect.ImmutableList;
+
+public class SingleSourceShortestPathApiTest extends TraverserApiTest {
+
+    @BeforeClass
+    public static void initShortestPathGraph() {
+        schema().propertyKey("weight")
+                .asDouble()
+                .ifNotExist()
+                .create();
+
+        schema().vertexLabel("node")
+                .useCustomizeStringId()
+                .ifNotExist()
+                .create();
+
+        schema().edgeLabel("link")
+                .sourceLabel("node").targetLabel("node")
+                .properties("weight")
+                .ifNotExist()
+                .create();
+
+        schema().edgeLabel("relateTo")
+                .sourceLabel("node").targetLabel("node")
+                .properties("weight")
+                .ifNotExist()
+                .create();
+
+        Vertex va = graph().addVertex(T.label, "node", T.id, "A");
+        Vertex vb = graph().addVertex(T.label, "node", T.id, "B");
+        Vertex vc = graph().addVertex(T.label, "node", T.id, "C");
+        Vertex vd = graph().addVertex(T.label, "node", T.id, "D");
+        Vertex ve = graph().addVertex(T.label, "node", T.id, "E");
+        Vertex vf = graph().addVertex(T.label, "node", T.id, "F");
+        Vertex vg = graph().addVertex(T.label, "node", T.id, "G");
+        Vertex vh = graph().addVertex(T.label, "node", T.id, "H");
+        Vertex vi = graph().addVertex(T.label, "node", T.id, "I");
+        Vertex vj = graph().addVertex(T.label, "node", T.id, "J");
+        Vertex vk = graph().addVertex(T.label, "node", T.id, "K");
+        Vertex vl = graph().addVertex(T.label, "node", T.id, "L");
+        Vertex vm = graph().addVertex(T.label, "node", T.id, "M");
+        Vertex vn = graph().addVertex(T.label, "node", T.id, "N");
+        Vertex vo = graph().addVertex(T.label, "node", T.id, "O");
+        Vertex vp = graph().addVertex(T.label, "node", T.id, "P");
+        Vertex vq = graph().addVertex(T.label, "node", T.id, "Q");
+        Vertex vr = graph().addVertex(T.label, "node", T.id, "R");
+        Vertex vz = graph().addVertex(T.label, "node", T.id, "Z");
+
+        /*
+         *   "link":
+         *   A --0.2--> B --0.4--> C --0.8--> D --0.6--> Z
+         *     ----------------10---------------------->
+         *     <--0.5-- E <--0.3-- F <--0.4-- G <--0.1--
+         *     <-------------------8--------------------
+         *     --0.1--> H --0.1--> I <--0.1-- J <--0.2--
+         *     -----0.4----> K -----0.5-----> L <--0.3--
+         *   "relateTo":
+         *     -----1.4-----> M -----3.8----> N --3.5-->
+         *     <----2.2------ O <----3.3----- P <-1.6---
+         *     -----3.1-----> Q <----2.0----- R --1.3-->
+         */
+        va.addEdge("link", vb, "weight", 0.2D);
+        vb.addEdge("link", vc, "weight", 0.4D);
+        vc.addEdge("link", vd, "weight", 0.8D);
+        vd.addEdge("link", vz, "weight", 0.6D);
+
+        va.addEdge("link", vz, "weight", 10.0D);
+
+        vz.addEdge("link", vg, "weight", 0.1D);
+        vg.addEdge("link", vf, "weight", 0.4D);
+        vf.addEdge("link", ve, "weight", 0.3D);
+        ve.addEdge("link", va, "weight", 0.5D);
+
+        vz.addEdge("link", va, "weight", 8.0D);
+
+        va.addEdge("link", vh, "weight", 0.1D);
+        vh.addEdge("link", vi, "weight", 0.1D);
+        vz.addEdge("link", vj, "weight", 0.2D);
+        vj.addEdge("link", vi, "weight", 0.1D);
+
+        va.addEdge("link", vk, "weight", 0.4D);
+        vk.addEdge("link", vl, "weight", 0.5D);
+        vz.addEdge("link", vl, "weight", 0.3D);
+
+        va.addEdge("relateTo", vm, "weight", 1.4D);
+        vm.addEdge("relateTo", vn, "weight", 3.8D);
+        vn.addEdge("relateTo", vz, "weight", 3.5D);
+
+        vz.addEdge("relateTo", vp, "weight", 1.6D);
+        vp.addEdge("relateTo", vo, "weight", 3.3D);
+        vo.addEdge("relateTo", va, "weight", 2.2D);
+
+        va.addEdge("relateTo", vq, "weight", 3.1D);
+        vr.addEdge("relateTo", vq, "weight", 2.0D);
+        vr.addEdge("relateTo", vz, "weight", 1.3D);
+    }
+
+    @Test
+    public void testSingleSourceShortestPath() {
+        WeightedPaths weightedPaths = singleSourceShortestPathAPI.get(
+                                      "A", Direction.BOTH, null, "weight",
+                                      -1, 0, -1, -1, false);
+        Assert.assertEquals(18, weightedPaths.paths().size());
+
+        WeightedPath.Path path = weightedPaths.paths().get("B");
+        Assert.assertEquals(0.2D, path.weight(), Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "B"), path.vertices());
+
+        path = weightedPaths.paths().get("C");
+        Assert.assertEquals(0.6000000000000001D, path.weight(),
+                            Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "B", "C"), path.vertices());
+
+        path = weightedPaths.paths().get("D");
+        Assert.assertEquals(1.1D, path.weight(), Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "H", "I", "J", "Z", "D"),
+                            path.vertices());
+
+        path = weightedPaths.paths().get("E");
+        Assert.assertEquals(0.5D, path.weight(), Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "E"), path.vertices());
+
+        path = weightedPaths.paths().get("F");
+        Assert.assertEquals(0.8D, path.weight(), Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "E", "F"), path.vertices());
+
+        path = weightedPaths.paths().get("G");
+        Assert.assertEquals(0.6D, path.weight(), Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "H", "I", "J", "Z", "G"),
+                            path.vertices());
+
+        path = weightedPaths.paths().get("H");
+        Assert.assertEquals(0.1D, path.weight(), Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "H"), path.vertices());
+
+        path = weightedPaths.paths().get("I");
+        Assert.assertEquals(0.2D, path.weight(), Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "H", "I"), path.vertices());
+
+        path = weightedPaths.paths().get("J");
+        Assert.assertEquals(0.30000000000000004D, path.weight(),
+                            Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "H", "I", "J"),
+                            path.vertices());
+
+        path = weightedPaths.paths().get("K");
+        Assert.assertEquals(0.4D, path.weight(), Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "K"), path.vertices());
+
+        path = weightedPaths.paths().get("L");
+        Assert.assertEquals(0.8D, path.weight(), Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "H", "I", "J", "Z", "L"),
+                            path.vertices());
+
+        path = weightedPaths.paths().get("M");
+        Assert.assertEquals(1.4D, path.weight(), Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "M"), path.vertices());
+
+        path = weightedPaths.paths().get("N");
+        Assert.assertEquals(4.0D, path.weight(), Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "H", "I", "J", "Z", "N"),
+                            path.vertices());
+
+        path = weightedPaths.paths().get("O");
+        Assert.assertEquals(2.2D, path.weight(), Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "O"), path.vertices());
+
+        path = weightedPaths.paths().get("P");
+        Assert.assertEquals(2.1D, path.weight(), Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "H", "I", "J", "Z", "P"),
+                            path.vertices());
+
+        path = weightedPaths.paths().get("Q");
+        Assert.assertEquals(3.1D, path.weight(), Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "Q"), path.vertices());
+
+        path = weightedPaths.paths().get("R");
+        Assert.assertEquals(1.8D, path.weight(), Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "H", "I", "J", "Z", "R"),
+                            path.vertices());
+
+        path = weightedPaths.paths().get("Z");
+        Assert.assertEquals(0.5D, path.weight(), Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "H", "I", "J", "Z"),
+                            path.vertices());
+    }
+
+    @Test
+    public void testSingleSourceShortestPathWithLabel() {
+        WeightedPaths weightedPaths = singleSourceShortestPathAPI.get(
+                                      "A", Direction.BOTH, "link", "weight",
+                                      -1, 0, -1, -1, false);
+        Assert.assertEquals(12, weightedPaths.paths().size());
+
+        WeightedPath.Path path = weightedPaths.paths().get("B");
+        Assert.assertEquals(0.2D, path.weight(), Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "B"), path.vertices());
+
+        path = weightedPaths.paths().get("C");
+        Assert.assertEquals(0.6000000000000001D, path.weight(),
+                            Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "B", "C"), path.vertices());
+
+        path = weightedPaths.paths().get("D");
+        Assert.assertEquals(1.1D, path.weight(), Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "H", "I", "J", "Z", "D"),
+                            path.vertices());
+
+        path = weightedPaths.paths().get("E");
+        Assert.assertEquals(0.5D, path.weight(), Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "E"), path.vertices());
+
+        path = weightedPaths.paths().get("F");
+        Assert.assertEquals(0.8D, path.weight(), Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "E", "F"), path.vertices());
+
+        path = weightedPaths.paths().get("G");
+        Assert.assertEquals(0.6D, path.weight(), Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "H", "I", "J", "Z", "G"),
+                            path.vertices());
+
+        path = weightedPaths.paths().get("H");
+        Assert.assertEquals(0.1D, path.weight(), Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "H"), path.vertices());
+
+        path = weightedPaths.paths().get("I");
+        Assert.assertEquals(0.2D, path.weight(), Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "H", "I"), path.vertices());
+
+        path = weightedPaths.paths().get("J");
+        Assert.assertEquals(0.30000000000000004D, path.weight(),
+                            Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "H", "I", "J"),
+                            path.vertices());
+
+        path = weightedPaths.paths().get("K");
+        Assert.assertEquals(0.4D, path.weight(), Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "K"), path.vertices());
+
+        path = weightedPaths.paths().get("L");
+        Assert.assertEquals(0.8D, path.weight(), Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "H", "I", "J", "Z", "L"),
+                            path.vertices());
+
+        path = weightedPaths.paths().get("Z");
+        Assert.assertEquals(0.5D, path.weight(), Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "H", "I", "J", "Z"),
+                            path.vertices());
+
+        weightedPaths = singleSourceShortestPathAPI.get(
+                        "A", Direction.BOTH, "relateTo", "weight",
+                        -1, 0, -1, -1, false);
+
+        Assert.assertEquals(7, weightedPaths.paths().size());
+
+        path = weightedPaths.paths().get("M");
+        Assert.assertEquals(1.4D, path.weight(), Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "M"), path.vertices());
+
+        path = weightedPaths.paths().get("N");
+        Assert.assertEquals(5.199999999999999D, path.weight(),
+                            Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "M", "N"),
+                            path.vertices());
+
+        path = weightedPaths.paths().get("O");
+        Assert.assertEquals(2.2D, path.weight(), Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "O"), path.vertices());
+
+        path = weightedPaths.paths().get("P");
+        Assert.assertEquals(5.5D, path.weight(), Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "O", "P"),
+                            path.vertices());
+
+        path = weightedPaths.paths().get("Q");
+        Assert.assertEquals(3.1D, path.weight(), Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "Q"), path.vertices());
+
+        path = weightedPaths.paths().get("R");
+        Assert.assertEquals(5.1D, path.weight(), Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "Q", "R"),
+                            path.vertices());
+
+        path = weightedPaths.paths().get("Z");
+        Assert.assertEquals(6.3999999999999995D, path.weight(),
+                            Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "Q", "R", "Z"),
+                            path.vertices());
+    }
+
+    @Test
+    public void testSingleSourceShortestPathWithDirection() {
+        WeightedPaths weightedPaths = singleSourceShortestPathAPI.get(
+                                      "A", Direction.OUT, null, "weight",
+                                      -1, 0, -1, -1, false);
+
+        Assert.assertEquals(17, weightedPaths.paths().size());
+
+        WeightedPath.Path path = weightedPaths.paths().get("B");
+        Assert.assertEquals(0.2D, path.weight(), Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "B"), path.vertices());
+
+        path = weightedPaths.paths().get("C");
+        Assert.assertEquals(0.6000000000000001D, path.weight(),
+                            Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "B", "C"), path.vertices());
+
+        path = weightedPaths.paths().get("D");
+        Assert.assertEquals(1.4000000000000001D, path.weight(),
+                            Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "B", "C", "D"),
+                            path.vertices());
+
+        path = weightedPaths.paths().get("E");
+        Assert.assertEquals(2.8D, path.weight(), Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "B", "C", "D",
+                                             "Z", "G", "F", "E"),
+                            path.vertices());
+
+        path = weightedPaths.paths().get("F");
+        Assert.assertEquals(2.5D, path.weight(), Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "B", "C", "D", "Z", "G", "F"),
+                            path.vertices());
+
+        path = weightedPaths.paths().get("G");
+        Assert.assertEquals(2.1D, path.weight(), Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "B", "C", "D", "Z", "G"),
+                            path.vertices());
+
+        path = weightedPaths.paths().get("H");
+        Assert.assertEquals(0.1D, path.weight(), Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "H"), path.vertices());
+
+        path = weightedPaths.paths().get("I");
+        Assert.assertEquals(0.2D, path.weight(), Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "H", "I"), path.vertices());
+
+        path = weightedPaths.paths().get("J");
+        Assert.assertEquals(2.2D, path.weight(),
+                            Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "B", "C", "D", "Z", "J"),
+                            path.vertices());
+
+        path = weightedPaths.paths().get("K");
+        Assert.assertEquals(0.4D, path.weight(), Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "K"), path.vertices());
+
+        path = weightedPaths.paths().get("L");
+        Assert.assertEquals(0.9D, path.weight(), Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "K", "L"),
+                            path.vertices());
+
+        path = weightedPaths.paths().get("M");
+        Assert.assertEquals(1.4D, path.weight(), Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "M"), path.vertices());
+
+        path = weightedPaths.paths().get("N");
+        Assert.assertEquals(5.199999999999999D, path.weight(),
+                            Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "M", "N"),
+                            path.vertices());
+
+        path = weightedPaths.paths().get("O");
+        Assert.assertEquals(6.9D, path.weight(), Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "B", "C", "D", "Z", "P", "O"),
+                            path.vertices());
+
+        path = weightedPaths.paths().get("P");
+        Assert.assertEquals(3.6D, path.weight(), Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "B", "C", "D", "Z", "P"),
+                            path.vertices());
+
+        path = weightedPaths.paths().get("Q");
+        Assert.assertEquals(3.1D, path.weight(), Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "Q"), path.vertices());
+
+        path = weightedPaths.paths().get("Z");
+        Assert.assertEquals(2.0D, path.weight(), Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "B", "C", "D", "Z"),
+                            path.vertices());
+    }
+
+    @Test
+    public void testSingleSourceShortestPathWithDegree() {
+        WeightedPaths weightedPaths = singleSourceShortestPathAPI.get(
+                                      "A", Direction.OUT, null, "weight",
+                                      1, 0, -1, -1, false);
+        Assert.assertEquals(4, weightedPaths.paths().size());
+
+        WeightedPath.Path path = weightedPaths.paths().get("B");
+        Assert.assertEquals(0.2D, path.weight(), Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "B"), path.vertices());
+
+        path = weightedPaths.paths().get("C");
+        Assert.assertEquals(0.6000000000000001D, path.weight(),
+                            Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "B", "C"), path.vertices());
+
+        path = weightedPaths.paths().get("D");
+        Assert.assertEquals(1.4000000000000001D, path.weight(),
+                            Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "B", "C", "D"),
+                            path.vertices());
+
+        path = weightedPaths.paths().get("Z");
+        Assert.assertEquals(2.0D, path.weight(), Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "B", "C", "D", "Z"),
+                            path.vertices());
+    }
+
+    @Test
+    public void testSingleSourceShortestPathWithLimit() {
+        WeightedPaths weightedPaths = singleSourceShortestPathAPI.get(
+                                      "A", Direction.BOTH, null, "weight",
+                                      -1, 0, -1, 11, false);
+        Assert.assertEquals(11, weightedPaths.paths().size());
+
+        WeightedPath.Path path = weightedPaths.paths().get("B");
+        Assert.assertEquals(0.2D, path.weight(), Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "B"), path.vertices());
+
+        path = weightedPaths.paths().get("C");
+        Assert.assertEquals(0.6000000000000001D, path.weight(),
+                            Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "B", "C"), path.vertices());
+
+        path = weightedPaths.paths().get("E");
+        Assert.assertEquals(0.5D, path.weight(), Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "E"), path.vertices());
+
+        path = weightedPaths.paths().get("F");
+        Assert.assertEquals(0.8D, path.weight(), Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "E", "F"), path.vertices());
+
+        path = weightedPaths.paths().get("L");
+        Assert.assertEquals(0.8D, path.weight(), Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "H", "I", "J", "Z", "L"),
+                            path.vertices());
+
+        path = weightedPaths.paths().get("G");
+        Assert.assertEquals(0.6D, path.weight(), Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "H", "I", "J", "Z", "G"),
+                            path.vertices());
+
+        path = weightedPaths.paths().get("H");
+        Assert.assertEquals(0.1D, path.weight(), Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "H"), path.vertices());
+
+        path = weightedPaths.paths().get("I");
+        Assert.assertEquals(0.2D, path.weight(), Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "H", "I"), path.vertices());
+
+        path = weightedPaths.paths().get("J");
+        Assert.assertEquals(0.30000000000000004D, path.weight(),
+                            Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "H", "I", "J"),
+                            path.vertices());
+
+        path = weightedPaths.paths().get("K");
+        Assert.assertEquals(0.4D, path.weight(), Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "K"), path.vertices());
+
+        path = weightedPaths.paths().get("Z");
+        Assert.assertEquals(0.5D, path.weight(), Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "H", "I", "J", "Z"),
+                            path.vertices());
+    }
+
+    @Test
+    public void testSingleSourceShortestPathWithVertex() {
+        WeightedPaths weightedPaths = singleSourceShortestPathAPI.get(
+                                      "A", Direction.BOTH, null, "weight",
+                                      -1, 0, -1, -1, true);
+        Assert.assertEquals(18, weightedPaths.paths().size());
+        Assert.assertEquals(19, weightedPaths.vertices().size());
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/api/traverser/TemplatePathsApiTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/traverser/TemplatePathsApiTest.java
new file mode 100644
index 0000000..e7504bf
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/traverser/TemplatePathsApiTest.java
@@ -0,0 +1,317 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.traverser;
+
+import java.util.List;
+import java.util.Set;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.baidu.hugegraph.structure.constant.Direction;
+import com.baidu.hugegraph.structure.constant.T;
+import com.baidu.hugegraph.structure.graph.Vertex;
+import com.baidu.hugegraph.structure.traverser.PathsWithVertices;
+import com.baidu.hugegraph.structure.traverser.TemplatePathsRequest;
+import com.baidu.hugegraph.testutil.Assert;
+import com.google.common.collect.ImmutableList;
+
+public class TemplatePathsApiTest extends TraverserApiTest {
+
+    @BeforeClass
+    public static void initShortestPathGraph() {
+        schema().propertyKey("weight")
+                .asDouble()
+                .ifNotExist()
+                .create();
+
+        schema().vertexLabel("node")
+                .useCustomizeNumberId()
+                .ifNotExist()
+                .create();
+
+        schema().edgeLabel("link")
+                .sourceLabel("node").targetLabel("node")
+                .ifNotExist()
+                .create();
+        schema().edgeLabel("relateTo")
+                .sourceLabel("node").targetLabel("node")
+                .properties("weight")
+                .ifNotExist()
+                .create();
+
+        Vertex v1 = graph().addVertex(T.label, "node", T.id, 1);
+        Vertex v2 = graph().addVertex(T.label, "node", T.id, 2);
+        Vertex v3 = graph().addVertex(T.label, "node", T.id, 3);
+        Vertex v4 = graph().addVertex(T.label, "node", T.id, 4);
+        Vertex v5 = graph().addVertex(T.label, "node", T.id, 5);
+        Vertex v6 = graph().addVertex(T.label, "node", T.id, 6);
+        Vertex v7 = graph().addVertex(T.label, "node", T.id, 7);
+        Vertex v8 = graph().addVertex(T.label, "node", T.id, 8);
+        Vertex v9 = graph().addVertex(T.label, "node", T.id, 9);
+        Vertex v10 = graph().addVertex(T.label, "node", T.id, 10);
+        Vertex v11 = graph().addVertex(T.label, "node", T.id, 11);
+        Vertex v12 = graph().addVertex(T.label, "node", T.id, 12);
+        Vertex v13 = graph().addVertex(T.label, "node", T.id, 13);
+        Vertex v14 = graph().addVertex(T.label, "node", T.id, 14);
+        Vertex v15 = graph().addVertex(T.label, "node", T.id, 15);
+        Vertex v16 = graph().addVertex(T.label, "node", T.id, 16);
+        Vertex v17 = graph().addVertex(T.label, "node", T.id, 17);
+
+        v1.addEdge("link", v2);
+        v2.addEdge("link", v3);
+        v3.addEdge("link", v4);
+        v4.addEdge("link", v5);
+        v5.addEdge("link", v6);
+        v6.addEdge("link", v7);
+        v8.addEdge("link", v7);
+        v9.addEdge("link", v8);
+        v10.addEdge("link", v9);
+
+        v1.addEdge("link", v11);
+        v11.addEdge("link", v12);
+        v12.addEdge("link", v13);
+        v13.addEdge("link", v14);
+        v10.addEdge("link", v15);
+        v15.addEdge("link", v14);
+
+        v1.addEdge("link", v16);
+        v16.addEdge("link", v17);
+        v10.addEdge("link", v17);
+
+        v1.addEdge("relateTo", v16, "weight", 0.8D);
+        v16.addEdge("relateTo", v17, "weight", 0.5D);
+        v10.addEdge("relateTo", v17, "weight", 0.6D);
+        v17.addEdge("relateTo", v16, "weight", 0.3D);
+    }
+
+    @Test
+    public void testTemplatePaths() {
+        TemplatePathsRequest.Builder builder = TemplatePathsRequest.builder();
+        builder.sources().ids(1);
+        builder.targets().ids(10);
+        builder.steps().direction(Direction.OUT).maxTimes(3);
+        builder.steps().direction(Direction.OUT).maxTimes(3);
+        builder.steps().direction(Direction.IN).maxTimes(3);
+        TemplatePathsRequest request = builder.build();
+        PathsWithVertices pathsWithVertices = templatePathsAPI.post(request);
+        List<PathsWithVertices.Paths> paths = pathsWithVertices.paths();
+        Assert.assertEquals(3, paths.size());
+        List<List<Object>> expected = ImmutableList.of(
+                ImmutableList.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10),
+                ImmutableList.of(1, 11, 12, 13, 14, 15, 10),
+                ImmutableList.of(1, 16, 17, 10)
+        );
+        for (PathsWithVertices.Paths path : paths) {
+            Assert.assertTrue(expected.contains(path.objects()));
+        }
+    }
+
+    @Test
+    public void testTemplatePathsWithVertex() {
+        TemplatePathsRequest.Builder builder = TemplatePathsRequest.builder();
+        builder.sources().ids(1);
+        builder.targets().ids(10);
+        builder.steps().direction(Direction.OUT).labels("relateTo").maxTimes(3);
+        builder.steps().direction(Direction.OUT).labels("relateTo").maxTimes(3);
+        builder.steps().direction(Direction.IN).labels("relateTo").maxTimes(3);
+        builder.withVertex(true);
+        TemplatePathsRequest request = builder.build();
+        PathsWithVertices pathsWithVertices = templatePathsAPI.post(request);
+        List<PathsWithVertices.Paths> paths = pathsWithVertices.paths();
+        Assert.assertEquals(1, paths.size());
+        List<Object> expectedIds = ImmutableList.of(1, 16, 17, 10);
+        List<List<Object>> expectedPath = ImmutableList.of(expectedIds);
+        for (PathsWithVertices.Paths path : paths) {
+            Assert.assertTrue(expectedPath.contains(path.objects()));
+        }
+        Set<Vertex> vertices = pathsWithVertices.vertices();
+        Assert.assertEquals(4, vertices.size());
+        for (Vertex v : vertices) {
+            Assert.assertTrue(expectedIds.contains(v.id()));
+        }
+    }
+
+    @Test
+    public void testTemplatePathsWithLabel() {
+        TemplatePathsRequest.Builder builder = TemplatePathsRequest.builder();
+        builder.sources().ids(1);
+        builder.targets().ids(10);
+        builder.steps().direction(Direction.OUT).labels("link").maxTimes(3);
+        builder.steps().direction(Direction.OUT).labels("link").maxTimes(3);
+        builder.steps().direction(Direction.IN).labels("link").maxTimes(3);
+        TemplatePathsRequest request = builder.build();
+        PathsWithVertices pathsWithVertices = templatePathsAPI.post(request);
+        List<PathsWithVertices.Paths> paths = pathsWithVertices.paths();
+        Assert.assertEquals(3, paths.size());
+        List<List<Object>> expected = ImmutableList.of(
+                ImmutableList.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10),
+                ImmutableList.of(1, 11, 12, 13, 14, 15, 10),
+                ImmutableList.of(1, 16, 17, 10)
+        );
+        for (PathsWithVertices.Paths path : paths) {
+            Assert.assertTrue(expected.contains(path.objects()));
+        }
+
+        builder = TemplatePathsRequest.builder();
+        builder.sources().ids(1);
+        builder.targets().ids(10);
+        builder.steps().direction(Direction.OUT).labels("relateTo").maxTimes(3);
+        builder.steps().direction(Direction.OUT).labels("relateTo").maxTimes(3);
+        builder.steps().direction(Direction.IN).labels("relateTo").maxTimes(3);
+        request = builder.build();
+        pathsWithVertices = templatePathsAPI.post(request);
+        paths = pathsWithVertices.paths();
+        Assert.assertEquals(1, paths.size());
+        expected = ImmutableList.of(
+                ImmutableList.of(1, 16, 17, 10)
+        );
+        for (PathsWithVertices.Paths path : paths) {
+            Assert.assertTrue(expected.contains(path.objects()));
+        }
+    }
+
+    @Test
+    public void testTemplatePathsWithRing() {
+        TemplatePathsRequest.Builder builder = TemplatePathsRequest.builder();
+        builder.sources().ids(1);
+        builder.targets().ids(10);
+        builder.steps().direction(Direction.OUT).labels("relateTo").maxTimes(3);
+        builder.steps().direction(Direction.OUT).labels("relateTo").maxTimes(3);
+        builder.steps().direction(Direction.IN).labels("relateTo").maxTimes(3);
+        TemplatePathsRequest request = builder.build();
+        PathsWithVertices pathsWithVertices = templatePathsAPI.post(request);
+        List<PathsWithVertices.Paths> paths = pathsWithVertices.paths();
+        Assert.assertEquals(1, paths.size());
+        List<List<Object>> expected = ImmutableList.of(
+                ImmutableList.of(1, 16, 17, 10)
+        );
+        for (PathsWithVertices.Paths path : paths) {
+            Assert.assertTrue(expected.contains(path.objects()));
+        }
+
+        builder = TemplatePathsRequest.builder();
+        builder.sources().ids(1);
+        builder.targets().ids(10);
+        builder.steps().direction(Direction.OUT).labels("relateTo").maxTimes(3);
+        builder.steps().direction(Direction.OUT).labels("relateTo").maxTimes(3);
+        builder.steps().direction(Direction.IN).labels("relateTo").maxTimes(3);
+        builder.withRing(true);
+        request = builder.build();
+        pathsWithVertices = templatePathsAPI.post(request);
+        paths = pathsWithVertices.paths();
+        Assert.assertEquals(4, paths.size());
+        expected = ImmutableList.of(
+                ImmutableList.of(1, 16, 17, 10),
+                ImmutableList.of(1, 16, 17, 16, 17, 10),
+                ImmutableList.of(1, 16, 17, 16, 17, 16, 17, 10),
+                ImmutableList.of(1, 16, 17, 16, 17, 16, 17, 16, 17, 10)
+        );
+        for (PathsWithVertices.Paths path : paths) {
+            Assert.assertTrue(expected.contains(path.objects()));
+        }
+    }
+
+    @Test
+    public void testTemplatePathsWithProperties() {
+        TemplatePathsRequest.Builder builder = TemplatePathsRequest.builder();
+        builder.sources().ids(1);
+        builder.targets().ids(10);
+        builder.steps().direction(Direction.OUT).labels("relateTo").maxTimes(3);
+        builder.steps().direction(Direction.OUT).labels("relateTo").maxTimes(3);
+        builder.steps().direction(Direction.IN).labels("relateTo").maxTimes(3);
+        builder.withRing(true);
+        TemplatePathsRequest request = builder.build();
+        PathsWithVertices pathsWithVertices = templatePathsAPI.post(request);
+        List<PathsWithVertices.Paths> paths = pathsWithVertices.paths();
+        Assert.assertEquals(4, paths.size());
+        List<List<Object>> expected = ImmutableList.of(
+                ImmutableList.of(1, 16, 17, 10),
+                ImmutableList.of(1, 16, 17, 16, 17, 10),
+                ImmutableList.of(1, 16, 17, 16, 17, 16, 17, 10),
+                ImmutableList.of(1, 16, 17, 16, 17, 16, 17, 16, 17, 10)
+        );
+        for (PathsWithVertices.Paths path : paths) {
+            Assert.assertTrue(expected.contains(path.objects()));
+        }
+
+        builder = TemplatePathsRequest.builder();
+        builder.sources().ids(1);
+        builder.targets().ids(10);
+        builder.steps().direction(Direction.OUT).labels("relateTo")
+               .properties("weight", "P.gt(0.4)").maxTimes(3);
+        builder.steps().direction(Direction.OUT).labels("relateTo")
+               .properties("weight", "P.gt(0.4)").maxTimes(3);
+        builder.steps().direction(Direction.IN).labels("relateTo")
+               .properties("weight", "P.gt(0.4)").maxTimes(3);
+        builder.withRing(true);
+        request = builder.build();
+        pathsWithVertices = templatePathsAPI.post(request);
+        paths = pathsWithVertices.paths();
+        Assert.assertEquals(1, paths.size());
+        expected = ImmutableList.of(
+                ImmutableList.of(1, 16, 17, 10)
+        );
+        for (PathsWithVertices.Paths path : paths) {
+            Assert.assertTrue(expected.contains(path.objects()));
+        }
+    }
+
+    @Test
+    public void testTemplatePathsWithLimit() {
+        TemplatePathsRequest.Builder builder = TemplatePathsRequest.builder();
+        builder.sources().ids(1);
+        builder.targets().ids(10);
+        builder.steps().direction(Direction.OUT).labels("link").maxTimes(3);
+        builder.steps().direction(Direction.OUT).labels("link").maxTimes(3);
+        builder.steps().direction(Direction.IN).labels("link").maxTimes(3);
+        TemplatePathsRequest request = builder.build();
+        PathsWithVertices pathsWithVertices = templatePathsAPI.post(request);
+        List<PathsWithVertices.Paths> paths = pathsWithVertices.paths();
+        Assert.assertEquals(3, paths.size());
+        List<List<Object>> expected = ImmutableList.of(
+                ImmutableList.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10),
+                ImmutableList.of(1, 11, 12, 13, 14, 15, 10),
+                ImmutableList.of(1, 16, 17, 10)
+        );
+        for (PathsWithVertices.Paths path : paths) {
+            Assert.assertTrue(expected.contains(path.objects()));
+        }
+
+        builder = TemplatePathsRequest.builder();
+        builder.sources().ids(1);
+        builder.targets().ids(10);
+        builder.steps().direction(Direction.OUT).labels("link").maxTimes(3);
+        builder.steps().direction(Direction.OUT).labels("link").maxTimes(3);
+        builder.steps().direction(Direction.IN).labels("link").maxTimes(3);
+        builder.limit(2);
+        request = builder.build();
+        pathsWithVertices = templatePathsAPI.post(request);
+        paths = pathsWithVertices.paths();
+        Assert.assertEquals(2, paths.size());
+        expected = ImmutableList.of(
+                ImmutableList.of(1, 16, 17, 10),
+                ImmutableList.of(1, 11, 12, 13, 14, 15, 10)
+        );
+        for (PathsWithVertices.Paths path : paths) {
+            Assert.assertTrue(expected.contains(path.objects()));
+        }
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/api/traverser/TraverserApiTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/traverser/TraverserApiTest.java
new file mode 100644
index 0000000..5100798
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/traverser/TraverserApiTest.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.traverser;
+
+import org.junit.BeforeClass;
+
+import com.baidu.hugegraph.api.BaseApiTest;
+
+public class TraverserApiTest extends BaseApiTest {
+
+    protected static SameNeighborsAPI sameNeighborsAPI;
+    protected static JaccardSimilarityAPI jaccardSimilarityAPI;
+    protected static ShortestPathAPI shortestPathAPI;
+    protected static AllShortestPathsAPI allShortestPathsAPI;
+    protected static SingleSourceShortestPathAPI singleSourceShortestPathAPI;
+    protected static WeightedShortestPathAPI weightedShortestPathAPI;
+    protected static MultiNodeShortestPathAPI multiNodeShortestPathAPI;
+    protected static PathsAPI pathsAPI;
+    protected static CrosspointsAPI crosspointsAPI;
+    protected static KoutAPI koutAPI;
+    protected static KneighborAPI kneighborAPI;
+
+    protected static RingsAPI ringsAPI;
+    protected static RaysAPI raysAPI;
+
+    protected static CountAPI countAPI;
+
+    protected static CustomizedPathsAPI customizedPathsAPI;
+    protected static CustomizedCrosspointsAPI customizedCrosspointsAPI;
+    protected static FusiformSimilarityAPI fusiformSimilarityAPI;
+    protected static TemplatePathsAPI templatePathsAPI;
+
+    protected static NeighborRankAPI neighborRankAPI;
+    protected static PersonalRankAPI personalRankAPI;
+
+    protected static VerticesAPI verticesAPI;
+    protected static EdgesAPI edgesAPI;
+
+    @BeforeClass
+    public static void init() {
+        BaseApiTest.init();
+
+        sameNeighborsAPI = new SameNeighborsAPI(client, GRAPH);
+        jaccardSimilarityAPI = new JaccardSimilarityAPI(client, GRAPH);
+        shortestPathAPI = new ShortestPathAPI(client, GRAPH);
+        allShortestPathsAPI = new AllShortestPathsAPI(client, GRAPH);
+        singleSourceShortestPathAPI = new SingleSourceShortestPathAPI(client,
+                                                                      GRAPH);
+        weightedShortestPathAPI = new WeightedShortestPathAPI(client, GRAPH);
+        multiNodeShortestPathAPI = new MultiNodeShortestPathAPI(client, GRAPH);
+        pathsAPI = new PathsAPI(client, GRAPH);
+        crosspointsAPI = new CrosspointsAPI(client, GRAPH);
+        koutAPI = new KoutAPI(client, GRAPH);
+        kneighborAPI = new KneighborAPI(client, GRAPH);
+
+        ringsAPI = new RingsAPI(client, GRAPH);
+        raysAPI = new RaysAPI(client, GRAPH);
+
+        countAPI = new CountAPI(client, GRAPH);
+
+        customizedPathsAPI = new CustomizedPathsAPI(client, GRAPH);
+        customizedCrosspointsAPI = new CustomizedCrosspointsAPI(client, GRAPH);
+        fusiformSimilarityAPI = new FusiformSimilarityAPI(client, GRAPH);
+        templatePathsAPI = new TemplatePathsAPI(client, GRAPH);
+
+        neighborRankAPI = new NeighborRankAPI(client, GRAPH);
+        personalRankAPI = new PersonalRankAPI(client, GRAPH);
+
+        verticesAPI = new VerticesAPI(client, GRAPH);
+        edgesAPI = new EdgesAPI(client, GRAPH);
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/api/traverser/WeightedShortestPathApiTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/traverser/WeightedShortestPathApiTest.java
new file mode 100644
index 0000000..1891145
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/api/traverser/WeightedShortestPathApiTest.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.api.traverser;
+
+import java.util.List;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.baidu.hugegraph.structure.constant.Direction;
+import com.baidu.hugegraph.structure.constant.T;
+import com.baidu.hugegraph.structure.graph.Vertex;
+import com.baidu.hugegraph.structure.traverser.WeightedPath;
+import com.baidu.hugegraph.testutil.Assert;
+import com.google.common.collect.ImmutableList;
+
+public class WeightedShortestPathApiTest extends TraverserApiTest {
+
+    @BeforeClass
+    public static void initShortestPathGraph() {
+        schema().propertyKey("weight")
+                .asDouble()
+                .ifNotExist()
+                .create();
+
+        schema().vertexLabel("node")
+                .useCustomizeStringId()
+                .ifNotExist()
+                .create();
+
+        schema().edgeLabel("link")
+                .sourceLabel("node").targetLabel("node")
+                .properties("weight")
+                .ifNotExist()
+                .create();
+
+        schema().edgeLabel("relateTo")
+                .sourceLabel("node").targetLabel("node")
+                .properties("weight")
+                .ifNotExist()
+                .create();
+
+        Vertex va = graph().addVertex(T.label, "node", T.id, "A");
+        Vertex vb = graph().addVertex(T.label, "node", T.id, "B");
+        Vertex vc = graph().addVertex(T.label, "node", T.id, "C");
+        Vertex vd = graph().addVertex(T.label, "node", T.id, "D");
+        Vertex ve = graph().addVertex(T.label, "node", T.id, "E");
+        Vertex vf = graph().addVertex(T.label, "node", T.id, "F");
+        Vertex vg = graph().addVertex(T.label, "node", T.id, "G");
+        Vertex vh = graph().addVertex(T.label, "node", T.id, "H");
+        Vertex vi = graph().addVertex(T.label, "node", T.id, "I");
+        Vertex vj = graph().addVertex(T.label, "node", T.id, "J");
+        Vertex vk = graph().addVertex(T.label, "node", T.id, "K");
+        Vertex vl = graph().addVertex(T.label, "node", T.id, "L");
+        Vertex vm = graph().addVertex(T.label, "node", T.id, "M");
+        Vertex vn = graph().addVertex(T.label, "node", T.id, "N");
+        Vertex vo = graph().addVertex(T.label, "node", T.id, "O");
+        Vertex vp = graph().addVertex(T.label, "node", T.id, "P");
+        Vertex vq = graph().addVertex(T.label, "node", T.id, "Q");
+        Vertex vr = graph().addVertex(T.label, "node", T.id, "R");
+        Vertex vz = graph().addVertex(T.label, "node", T.id, "Z");
+
+        /*
+         *   "link":
+         *   A --0.2--> B --0.4--> C --0.8--> D --0.6--> Z
+         *     ----------------10---------------------->
+         *     <--0.5-- E <--0.3-- F <--0.4-- G <--0.1--
+         *     <-------------------8--------------------
+         *     --0.1--> H --0.1--> I <--0.1-- J <--0.2--
+         *     -----0.4----> K -----0.5-----> L <--0.3--
+         *   "relateTo":
+         *     -----1.4-----> M -----3.8----> N --3.5-->
+         *     <----2.2------ O <----3.3----- P <-1.6---
+         *     -----3.1-----> Q <----2.0----- R --1.3-->
+         */
+        va.addEdge("link", vb, "weight", 0.2D);
+        vb.addEdge("link", vc, "weight", 0.4D);
+        vc.addEdge("link", vd, "weight", 0.8D);
+        vd.addEdge("link", vz, "weight", 0.6D);
+
+        va.addEdge("link", vz, "weight", 10.0D);
+
+        vz.addEdge("link", vg, "weight", 0.1D);
+        vg.addEdge("link", vf, "weight", 0.4D);
+        vf.addEdge("link", ve, "weight", 0.3D);
+        ve.addEdge("link", va, "weight", 0.5D);
+
+        vz.addEdge("link", va, "weight", 8.0D);
+
+        va.addEdge("link", vh, "weight", 0.1D);
+        vh.addEdge("link", vi, "weight", 0.1D);
+        vz.addEdge("link", vj, "weight", 0.2D);
+        vj.addEdge("link", vi, "weight", 0.1D);
+
+        va.addEdge("link", vk, "weight", 0.4D);
+        vk.addEdge("link", vl, "weight", 0.5D);
+        vz.addEdge("link", vl, "weight", 0.3D);
+
+        va.addEdge("relateTo", vm, "weight", 1.4D);
+        vm.addEdge("relateTo", vn, "weight", 3.8D);
+        vn.addEdge("relateTo", vz, "weight", 3.5D);
+
+        vz.addEdge("relateTo", vp, "weight", 1.6D);
+        vp.addEdge("relateTo", vo, "weight", 3.3D);
+        vo.addEdge("relateTo", va, "weight", 2.2D);
+
+        va.addEdge("relateTo", vq, "weight", 3.1D);
+        vr.addEdge("relateTo", vq, "weight", 2.0D);
+        vr.addEdge("relateTo", vz, "weight", 1.3D);
+    }
+
+    @Test
+    public void testWeightedShortestPath() {
+        WeightedPath weightedPath = weightedShortestPathAPI.get(
+                                     "A", "Z", Direction.BOTH, null,
+                                     "weight", -1, 0, -1, false);
+        Assert.assertTrue(weightedPath.vertices().isEmpty());
+        Assert.assertEquals(0.5D, weightedPath.path().weight(),
+                            Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "H", "I", "J", "Z"),
+                            weightedPath.path().vertices());
+    }
+
+    @Test
+    public void testWeightedShortestPathWithLabel() {
+        WeightedPath weightedPath = weightedShortestPathAPI.get(
+                                    "A", "Z", Direction.BOTH, "link",
+                                    "weight", -1, 0, -1, false);
+        Assert.assertTrue(weightedPath.vertices().isEmpty());
+        Assert.assertEquals(0.5D, weightedPath.path().weight(),
+                            Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "H", "I", "J", "Z"),
+                            weightedPath.path().vertices());
+
+        weightedPath = weightedShortestPathAPI.get(
+                       "A", "Z", Direction.BOTH, "relateTo",
+                       "weight", -1, 0, -1, false);
+        Assert.assertTrue(weightedPath.vertices().isEmpty());
+        Assert.assertEquals(6.3999999999999995D, weightedPath.path().weight(),
+                            Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "Q", "R", "Z"),
+                            weightedPath.path().vertices());
+    }
+
+    @Test
+    public void testWeightedShortestPathWithDirection() {
+        WeightedPath weightedPath = weightedShortestPathAPI.get(
+                                    "A", "Z", Direction.OUT, null,
+                                    "weight", -1, 0, -1, false);
+        Assert.assertTrue(weightedPath.vertices().isEmpty());
+        Assert.assertEquals(2.0D, weightedPath.path().weight(),
+                            Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "B", "C", "D", "Z"),
+                            weightedPath.path().vertices());
+    }
+
+    @Test
+    public void testWeightedShortestPathWithDegree() {
+        WeightedPath weightedPath = weightedShortestPathAPI.get(
+                                    "A", "Z", Direction.OUT, null,
+                                    "weight", 1L, 0L, -1L, false);
+        Assert.assertTrue(weightedPath.vertices().isEmpty());
+        Assert.assertEquals(2.0D, weightedPath.path().weight(),
+                            Double.MIN_VALUE);
+        Assert.assertEquals(ImmutableList.of("A", "B", "C", "D", "Z"),
+                            weightedPath.path().vertices());
+    }
+
+    @Test
+    public void testWeightedShortestPathWithVertex() {
+        WeightedPath weightedPath = weightedShortestPathAPI.get(
+                                    "A", "Z", Direction.BOTH, null, "weight",
+                                    -1, 0, -1, true);
+        Assert.assertEquals(5, weightedPath.vertices().size());
+        List<Object> expected = ImmutableList.of("A", "H", "I", "J", "Z");
+        for (Vertex vertex : weightedPath.vertices()) {
+            Assert.assertTrue(expected.contains(vertex.id()));
+        }
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/functional/AuthManagerTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/functional/AuthManagerTest.java
new file mode 100644
index 0000000..4c37b76
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/functional/AuthManagerTest.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.functional;
+
+import java.util.List;
+import java.util.Set;
+
+import org.apache.commons.collections.CollectionUtils;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.baidu.hugegraph.driver.HugeClient;
+import com.baidu.hugegraph.structure.auth.Access;
+import com.baidu.hugegraph.structure.auth.Belong;
+import com.baidu.hugegraph.structure.auth.Group;
+import com.baidu.hugegraph.structure.auth.HugePermission;
+import com.baidu.hugegraph.structure.auth.HugeResource;
+import com.baidu.hugegraph.structure.auth.HugeResourceType;
+import com.baidu.hugegraph.structure.auth.Login;
+import com.baidu.hugegraph.structure.auth.LoginResult;
+import com.baidu.hugegraph.structure.auth.Project;
+import com.baidu.hugegraph.structure.auth.Target;
+import com.baidu.hugegraph.structure.auth.TokenPayload;
+import com.baidu.hugegraph.structure.auth.User;
+import com.baidu.hugegraph.structure.auth.User.UserRole;
+import com.baidu.hugegraph.testutil.Assert;
+import com.google.common.collect.ImmutableSet;
+
+public class AuthManagerTest extends BaseFuncTest {
+
+    @Override
+    @Before
+    public void setup() {
+    }
+
+    @Override
+    @After
+    public void teardown() throws Exception {
+        auth().deleteAll();
+    }
+
+    @Test
+    public void testAuth() {
+        User user = new User();
+        user.name("bob");
+        user.password("123456");
+        user = auth().createUser(user);
+
+        Group group = new Group();
+        group.name("managers");
+        group = auth().createGroup(group);
+
+        Target gremlin = new Target();
+        gremlin.name("gremlin");
+        gremlin.graph("hugegraph");
+        gremlin.url("127.0.0.1:8080");
+        gremlin.resources(new HugeResource(HugeResourceType.GREMLIN));
+        gremlin = auth().createTarget(gremlin);
+
+        Target task = new Target();
+        task.name("task");
+        task.graph("hugegraph");
+        task.url("127.0.0.1:8080");
+        task.resources(new HugeResource(HugeResourceType.TASK));
+        task = auth().createTarget(task);
+
+        Belong belong = new Belong();
+        belong.user(user);
+        belong.group(group);
+        belong = auth().createBelong(belong);
+
+        Access access1 = new Access();
+        access1.group(group);
+        access1.target(gremlin);
+        access1.permission(HugePermission.EXECUTE);
+        access1 = auth().createAccess(access1);
+
+        Access access2 = new Access();
+        access2.group(group);
+        access2.target(task);
+        access2.permission(HugePermission.READ);
+        access2 = auth().createAccess(access2);
+
+        Project project1 = new Project("test");
+        project1 = auth().createProject(project1);
+        Assert.assertEquals("test", project1.name());
+
+        Project project2 = new Project("test2");
+        project2 = auth().createProject(project2);
+        Assert.assertEquals("test2", project2.name());
+
+        Project newProject1 = auth().getProject(project1);
+        Assert.assertEquals(newProject1.id(), project1.id());
+        Assert.assertTrue(CollectionUtils.isEmpty(newProject1.graphs()));
+
+        List<Project> projects = auth().listProjects();
+        Assert.assertNotNull(projects);
+        Assert.assertEquals(2, projects.size());
+
+        Set<String> graphs = ImmutableSet.of("graph1", "graph2");
+        newProject1 = auth().projectAddGraphs(project1, graphs);
+        Assert.assertNotNull(newProject1);
+        Assert.assertEquals(graphs, newProject1.graphs());
+
+        graphs = ImmutableSet.of("graph2");
+        newProject1 = auth().projectRemoveGraphs(project1,
+                                                 ImmutableSet.of("graph1"));
+        Assert.assertNotNull(newProject1);
+        Assert.assertEquals(graphs, newProject1.graphs());
+
+        Object project1Id = project1.id();
+        project1 = new Project(project1Id);
+        project1.description("test description");
+        newProject1 = auth().updateProject(project1);
+        Assert.assertEquals(newProject1.description(), project1.description());
+
+        auth().deleteProject(project2);
+        projects.remove(project2);
+        List<Project> newProjects = auth().listProjects();
+        Assert.assertEquals(newProjects, projects);
+
+        UserRole role = auth().getUserRole(user);
+        String r = "{\"roles\":{\"hugegraph\":" +
+                   "{\"READ\":[{\"type\":\"TASK\",\"label\":\"*\",\"properties\":null}]," +
+                   "\"EXECUTE\":[{\"type\":\"GREMLIN\",\"label\":\"*\",\"properties\":null}]}}}";
+        Assert.assertEquals(r, role.toString());
+
+        Login login = new Login();
+        login.name("bob");
+        login.password("123456");
+        LoginResult result = auth().login(login);
+
+        String token = result.token();
+
+        HugeClient client = baseClient();
+        client.setAuthContext("Bearer " + token);
+
+        TokenPayload payload = auth().verifyToken();
+        Assert.assertEquals("bob", payload.username());
+        Assert.assertEquals(user.id(), payload.userId());
+
+        auth().logout();
+        client.resetAuthContext();
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/functional/BaseFuncTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/functional/BaseFuncTest.java
new file mode 100644
index 0000000..301f3e8
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/functional/BaseFuncTest.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.functional;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+
+import com.baidu.hugegraph.BaseClientTest;
+
+public class BaseFuncTest extends BaseClientTest {
+
+    @BeforeClass
+    public static void init() {
+        BaseClientTest.init();
+        BaseFuncTest.clearData();
+    }
+
+    @AfterClass
+    public static void clear() throws Exception {
+        BaseFuncTest.clearData();
+        BaseClientTest.clear();
+    }
+
+    protected static void clearData() {
+        // Clear edge
+        graph().listEdges().forEach(e -> graph().removeEdge(e.id()));
+        // Clear vertex
+        graph().listVertices().forEach(v -> graph().removeVertex(v.id()));
+
+        List<Long> ilTaskIds = new ArrayList<>();
+        // Clear schema
+        schema().getIndexLabels().forEach(il -> {
+            ilTaskIds.add(schema().removeIndexLabelAsync(il.name()));
+        });
+        ilTaskIds.forEach(BaseFuncTest::waitUntilTaskCompleted);
+
+        List<Long> elTaskIds = new ArrayList<>();
+        schema().getEdgeLabels().forEach(el -> {
+            elTaskIds.add(schema().removeEdgeLabelAsync(el.name()));
+        });
+        elTaskIds.forEach(BaseFuncTest::waitUntilTaskCompleted);
+
+        List<Long> vlTaskIds = new ArrayList<>();
+        schema().getVertexLabels().forEach(vl -> {
+            vlTaskIds.add(schema().removeVertexLabelAsync(vl.name()));
+        });
+        vlTaskIds.forEach(BaseFuncTest::waitUntilTaskCompleted);
+
+        schema().getPropertyKeys().forEach(pk -> {
+            schema().removePropertyKey(pk.name());
+        });
+    }
+
+    protected static void runWithThreads(int threads, Runnable task) {
+        ExecutorService executor = Executors.newFixedThreadPool(threads);
+        List<Future<?>> futures = new ArrayList<>();
+        for (int i = 0; i < threads; i++) {
+            futures.add(executor.submit(task));
+        }
+        for (Future<?> future : futures) {
+            try {
+                future.get();
+            } catch (InterruptedException | ExecutionException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    protected static void waitUntilTaskCompleted(long taskId) {
+        task().waitUntilTaskCompleted(taskId, 3L);
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/functional/BatchInsertTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/functional/BatchInsertTest.java
new file mode 100644
index 0000000..b761ce0
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/functional/BatchInsertTest.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.functional;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.baidu.hugegraph.BaseClientTest;
+import com.baidu.hugegraph.exception.InvalidOperationException;
+import com.baidu.hugegraph.structure.graph.Edge;
+import com.baidu.hugegraph.structure.graph.Vertex;
+import com.baidu.hugegraph.testutil.Assert;
+
+public class BatchInsertTest extends BaseFuncTest {
+
+    private static final int BATCH_SIZE = 500;
+
+    @Before
+    public void setup() {
+        BaseClientTest.initPropertyKey();
+        BaseClientTest.initVertexLabel();
+        BaseClientTest.initEdgeLabel();
+    }
+
+    @After
+    public void teardown() throws Exception {
+        BaseFuncTest.clearData();
+    }
+
+    @Test
+    public void testBatchInsertInOneLoop() {
+        List<Vertex> vertices = new ArrayList<>(BATCH_SIZE);
+        List<Edge> edges = new ArrayList<>(BATCH_SIZE);
+        for (int i = 1; i <= BATCH_SIZE; i++) {
+            Vertex vertex1 = new Vertex("person").property("name", "P-" + i)
+                                                 .property("age", i);
+            Vertex vertex2 = new Vertex("software").property("name", "S-" + i)
+                                                   .property("lang", "java");
+            Edge edge = new Edge("created").source(vertex1).target(vertex2)
+                                           .property("date", "2018-12-25");
+
+            vertices.add(vertex1);
+            vertices.add(vertex2);
+            edges.add(edge);
+
+            if (vertices.size() >= BATCH_SIZE) {
+                graph().addVertices(vertices);
+                graph().addEdges(edges);
+
+                vertices.clear();
+                edges.clear();
+            }
+        }
+
+        Assert.assertEquals(2 * BATCH_SIZE, graph().listVertices(-1).size());
+        Assert.assertEquals(BATCH_SIZE, graph().listEdges(-1).size());
+    }
+
+    @Test
+    public void testBatchInsertInOneLoopButAddEdgesBeforeVertices() {
+        List<Vertex> vertices = new ArrayList<>(BATCH_SIZE);
+        List<Edge> edges = new ArrayList<>(BATCH_SIZE);
+        for (int i = 1; i <= BATCH_SIZE; i++) {
+            Vertex vertex1 = new Vertex("person").property("name", "P-" + i)
+                                                 .property("age", i);
+            Vertex vertex2 = new Vertex("software").property("name", "S-" + i)
+                                                   .property("lang", "java");
+            Edge edge = new Edge("created").source(vertex1).target(vertex2)
+                                           .property("date", "2018-12-25");
+
+            vertices.add(vertex1);
+            vertices.add(vertex2);
+            edges.add(edge);
+
+            if (vertices.size() >= BATCH_SIZE) {
+                // Must add vertices before edges
+                Assert.assertThrows(InvalidOperationException.class, () -> {
+                    graph().addEdges(edges);
+                });
+                graph().addVertices(vertices);
+
+                vertices.clear();
+                edges.clear();
+            }
+        }
+
+        Assert.assertEquals(2 * BATCH_SIZE, graph().listVertices(-1).size());
+        Assert.assertEquals(0, graph().listEdges(-1).size());
+    }
+
+    @Test
+    public void testBatchInsertInTwoLoops() {
+        int vertexCount = BATCH_SIZE;
+        List<Vertex> persons = new ArrayList<>(BATCH_SIZE);
+        List<Vertex> softwares = new ArrayList<>(BATCH_SIZE);
+        List<Edge> edges = new ArrayList<>(BATCH_SIZE);
+
+        for (int i = 1; i <= vertexCount; i++) {
+            Vertex person = new Vertex("person").property("name", "P-" + i)
+                                                .property("age", i);
+            Vertex software = new Vertex("software").property("name", "S-" + i)
+                                                    .property("lang", "java");
+
+            persons.add(person);
+            softwares.add(software);
+        }
+        graph().addVertices(persons);
+        graph().addVertices(softwares);
+
+        for (int i = 0; i < vertexCount; i++) {
+            Vertex person = persons.get(i);
+            Vertex software = softwares.get(i);
+
+            Edge edge = new Edge("created").source(person).target(software)
+                                           .property("date", "2018-12-25");
+
+            edges.add(edge);
+        }
+        graph().addEdges(edges);
+
+        Assert.assertEquals(2 * BATCH_SIZE, graph().listVertices(-1).size());
+        Assert.assertEquals(BATCH_SIZE, graph().listEdges(-1).size());
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/functional/EdgeLabelTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/functional/EdgeLabelTest.java
new file mode 100644
index 0000000..f77fc71
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/functional/EdgeLabelTest.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.functional;
+
+import java.util.Date;
+import java.util.List;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.baidu.hugegraph.driver.SchemaManager;
+import com.baidu.hugegraph.structure.Task;
+import com.baidu.hugegraph.structure.schema.EdgeLabel;
+import com.baidu.hugegraph.testutil.Assert;
+import com.baidu.hugegraph.util.DateUtil;
+import com.google.common.collect.ImmutableList;
+
+public class EdgeLabelTest extends BaseFuncTest {
+
+    @Before
+    public void setup() {
+        BaseFuncTest.initPropertyKey();
+        BaseFuncTest.initVertexLabel();
+    }
+
+    @After
+    public void teardown() throws Exception {
+        BaseFuncTest.clearData();
+    }
+
+    @Test
+    public void testLinkedVertexLabel() {
+        SchemaManager schema = schema();
+        EdgeLabel father = schema.edgeLabel("father")
+                                 .link("person", "person")
+                                 .properties("weight")
+                                 .userdata("multiplicity", "one-to-many")
+                                 .create();
+        EdgeLabel write = schema.edgeLabel("write")
+                                .link("person", "book")
+                                .properties("date", "weight")
+                                .userdata("multiplicity", "one-to-many")
+                                .userdata("multiplicity", "many-to-many")
+                                .create();
+
+        Assert.assertTrue(father.linkedVertexLabel("person"));
+        Assert.assertFalse(father.linkedVertexLabel("book"));
+        Assert.assertTrue(write.linkedVertexLabel("person"));
+        Assert.assertTrue(write.linkedVertexLabel("book"));
+    }
+
+    @Test
+    public void testAddEdgeLabelWithUserData() {
+        SchemaManager schema = schema();
+        EdgeLabel father = schema.edgeLabel("father")
+                                 .link("person", "person")
+                                 .properties("weight")
+                                 .userdata("multiplicity", "one-to-many")
+                                 .create();
+        Assert.assertEquals(2, father.userdata().size());
+        Assert.assertEquals("one-to-many",
+                            father.userdata().get("multiplicity"));
+        String time = (String) father.userdata().get("~create_time");
+        Date createTime = DateUtil.parse(time);
+        Assert.assertTrue(createTime.before(DateUtil.now()));
+
+        EdgeLabel write = schema.edgeLabel("write")
+                                .link("person", "book")
+                                .properties("date", "weight")
+                                .userdata("multiplicity", "one-to-many")
+                                .userdata("multiplicity", "many-to-many")
+                                .create();
+        // The same key user data will be overwritten
+        Assert.assertEquals(2, write.userdata().size());
+        Assert.assertEquals("many-to-many",
+                            write.userdata().get("multiplicity"));
+        time = (String) write.userdata().get("~create_time");
+        createTime = DateUtil.parse(time);
+        Assert.assertTrue(createTime.before(DateUtil.now()));
+    }
+
+    @Test
+    public void testAppendEdgeLabelWithUserData() {
+        SchemaManager schema = schema();
+        schema.vertexLabel("person")
+              .properties("name", "age", "city")
+              .primaryKeys("name")
+              .nullableKeys("city")
+              .ifNotExist()
+              .create();
+
+        EdgeLabel father = schema.edgeLabel("father")
+                                 .link("person", "person")
+                                 .properties("weight")
+                                 .create();
+        Assert.assertEquals(1, father.userdata().size());
+        String time = (String) father.userdata().get("~create_time");
+        Date createTime = DateUtil.parse(time);
+        Assert.assertTrue(createTime.before(DateUtil.now()));
+
+        father = schema.edgeLabel("father")
+                       .userdata("multiplicity", "one-to-many")
+                       .append();
+        Assert.assertEquals(2, father.userdata().size());
+        Assert.assertEquals("one-to-many",
+                            father.userdata().get("multiplicity"));
+        time = (String) father.userdata().get("~create_time");
+        Assert.assertEquals(createTime, DateUtil.parse(time));
+    }
+
+    @Test
+    public void testEliminateEdgeLabelWithUserData() {
+        SchemaManager schema = schema();
+        EdgeLabel write = schema.edgeLabel("write")
+                                .link("person", "book")
+                                .properties("date", "weight")
+                                .userdata("multiplicity", "one-to-many")
+                                .userdata("icon", "picture2")
+                                .create();
+        Assert.assertEquals(3, write.userdata().size());
+        Assert.assertEquals("one-to-many",
+                            write.userdata().get("multiplicity"));
+        Assert.assertEquals("picture2", write.userdata().get("icon"));
+        String time = (String) write.userdata().get("~create_time");
+        Date createTime = DateUtil.parse(time);
+        Assert.assertTrue(createTime.before(DateUtil.now()));
+
+        write = schema.edgeLabel("write")
+                      .userdata("icon", "")
+                      .eliminate();
+        Assert.assertEquals(2, write.userdata().size());
+        Assert.assertEquals("one-to-many",
+                            write.userdata().get("multiplicity"));
+        time = (String) write.userdata().get("~create_time");
+        Assert.assertEquals(createTime, DateUtil.parse(time));
+    }
+
+    @Test
+    public void testRemoveEdgeLabelSync() {
+        SchemaManager schema = schema();
+        EdgeLabel write = schema.edgeLabel("write")
+                                .link("person", "book")
+                                .properties("date", "weight")
+                                .userdata("multiplicity", "one-to-many")
+                                .userdata("icon", "picture2")
+                                .create();
+
+        Assert.assertNotNull(write);
+        // Remove edge label sync
+        schema.removeEdgeLabel("write");
+
+        write = schema.edgeLabel("write")
+                      .link("person", "book")
+                      .properties("date", "weight")
+                      .userdata("multiplicity", "one-to-many")
+                      .userdata("icon", "picture2")
+                      .create();
+
+        Assert.assertNotNull(write);
+        // Remove edge label sync with timeout
+        schema.removeEdgeLabel("write", 10);
+    }
+
+    @Test
+    public void testRemoveEdgeLabelAsync() {
+        SchemaManager schema = schema();
+        EdgeLabel write = schema.edgeLabel("write")
+                                .link("person", "book")
+                                .properties("date", "weight")
+                                .userdata("multiplicity", "one-to-many")
+                                .userdata("icon", "picture2")
+                                .create();
+        Assert.assertNotNull(write);
+        // Remove edge label async and wait
+        long taskId = schema.removeEdgeLabelAsync("write");
+        Task task = task().waitUntilTaskCompleted(taskId, 10);
+        Assert.assertTrue(task.completed());
+    }
+
+    @Test
+    public void testListByNames() {
+        SchemaManager schema = schema();
+        EdgeLabel father = schema.edgeLabel("father")
+                                 .link("person", "person")
+                                 .create();
+
+        EdgeLabel write = schema.edgeLabel("write")
+                                .link("person", "book")
+                                .create();
+
+        List<EdgeLabel> edgeLabels;
+
+        edgeLabels = schema.getEdgeLabels(ImmutableList.of("father"));
+        Assert.assertEquals(1, edgeLabels.size());
+        assertContains(edgeLabels, father);
+
+        edgeLabels = schema.getEdgeLabels(ImmutableList.of("write"));
+        Assert.assertEquals(1, edgeLabels.size());
+        assertContains(edgeLabels, write);
+
+        edgeLabels = schema.getEdgeLabels(ImmutableList.of("father", "write"));
+        Assert.assertEquals(2, edgeLabels.size());
+        assertContains(edgeLabels, father);
+        assertContains(edgeLabels, write);
+    }
+
+    @Test
+    public void testResetEdgeLabelId() {
+        SchemaManager schema = schema();
+        EdgeLabel write = schema.edgeLabel("write")
+                                .link("person", "book")
+                                .properties("date", "weight")
+                                .userdata("multiplicity", "one-to-many")
+                                .userdata("icon", "picture2")
+                                .create();
+        Assert.assertTrue(write.id() > 0);
+        write.resetId();
+        Assert.assertEquals(0L, write.id());
+    }
+
+    @Test
+    public void testSetCheckExist() {
+        SchemaManager schema = schema();
+        EdgeLabel write = schema.edgeLabel("write")
+                                .link("person", "book")
+                                .properties("date", "weight")
+                                .userdata("multiplicity", "one-to-many")
+                                .userdata("icon", "picture2")
+                                .create();
+        Assert.assertTrue(write.checkExist());
+        write.checkExist(false);
+        Assert.assertFalse(write.checkExist());
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/functional/EdgeTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/functional/EdgeTest.java
new file mode 100644
index 0000000..ae2ec0b
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/functional/EdgeTest.java
@@ -0,0 +1,799 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.functional;
+
+import java.text.ParseException;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.baidu.hugegraph.BaseClientTest;
+import com.baidu.hugegraph.driver.SchemaManager;
+import com.baidu.hugegraph.exception.InvalidOperationException;
+import com.baidu.hugegraph.exception.ServerException;
+import com.baidu.hugegraph.structure.constant.Direction;
+import com.baidu.hugegraph.structure.constant.T;
+import com.baidu.hugegraph.structure.graph.Edge;
+import com.baidu.hugegraph.structure.graph.Vertex;
+import com.baidu.hugegraph.structure.gremlin.Result;
+import com.baidu.hugegraph.structure.gremlin.ResultSet;
+import com.baidu.hugegraph.testutil.Assert;
+import com.baidu.hugegraph.testutil.Utils;
+import com.baidu.hugegraph.util.DateUtil;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterators;
+
+public class EdgeTest extends BaseFuncTest {
+
+    @Override
+    @Before
+    public void setup() {
+        BaseClientTest.initPropertyKey();
+        BaseClientTest.initVertexLabel();
+        BaseClientTest.initEdgeLabel();
+        BaseClientTest.initVertex();
+    }
+
+    @Override
+    @After
+    public void teardown() throws Exception {
+        BaseFuncTest.clearData();
+    }
+
+    @Test
+    public void testLinkedVertex() {
+        Object peterId = getVertexId("person", "name", "peter");
+        Object lopId = getVertexId("software", "name", "lop");
+
+        Edge created = graph().addEdge(peterId, "created", lopId,
+                                       "date", "2017-03-24");
+        Assert.assertTrue(created.linkedVertex(peterId));
+        Assert.assertTrue(created.linkedVertex(lopId));
+    }
+
+    @Test
+    public void testAddEdgeProperty() {
+        Object peterId = getVertexId("person", "name", "peter");
+        Object lopId = getVertexId("software", "name", "lop");
+
+        Edge created = graph().addEdge(peterId, "created", lopId,
+                                       "date", "2017-03-24");
+        Map<String, Object> props = ImmutableMap.of(
+                                    "date", Utils.formatDate("2017-03-24"));
+        Assert.assertEquals(props, created.properties());
+
+        created.property("city", "HongKong");
+        props = ImmutableMap.of("date", Utils.formatDate("2017-03-24"),
+                                "city", "HongKong");
+        Assert.assertEquals(props, created.properties());
+    }
+
+    @Test
+    public void testUpdateEdgeProperty() {
+        Object peterId = getVertexId("person", "name", "peter");
+        Object lopId = getVertexId("software", "name", "lop");
+
+        Edge created = graph().addEdge(peterId, "created", lopId,
+                                       "date", "2017-03-24");
+        Map<String, Object> props = ImmutableMap.of(
+                                    "date", Utils.formatDate("2017-03-24"));
+        Assert.assertEquals(props, created.properties());
+
+        created.property("date", "2017-08-08");
+        props = ImmutableMap.of("date", Utils.formatDate("2017-08-08"));
+        Assert.assertEquals(props, created.properties());
+    }
+
+    @Test
+    public void testAddEdgePropertyValueList() {
+        schema().propertyKey("time")
+                .asDate()
+                .valueList()
+                .ifNotExist()
+                .create();
+        schema().edgeLabel("created")
+                .properties("time")
+                .nullableKeys("time")
+                .append();
+
+        Object peterId = getVertexId("person", "name", "peter");
+        Object lopId = getVertexId("software", "name", "lop");
+
+        Edge created = graph().addEdge(peterId, "created", lopId,
+                                       "date", "2017-03-24",
+                                       "time", "2012-10-10");
+
+        Map<String, Object> props = ImmutableMap.of(
+                                    "date", Utils.formatDate("2017-03-24"),
+                                    "time", ImmutableList.of(
+                                            Utils.formatDate("2012-10-10")));
+        Assert.assertEquals(props, created.properties());
+
+        created.property("time", "2014-02-14");
+        props = ImmutableMap.of("date", Utils.formatDate("2017-03-24"),
+                                "time", ImmutableList.of(
+                                        Utils.formatDate("2012-10-10"),
+                                        Utils.formatDate("2014-02-14")));
+        Assert.assertEquals(props, created.properties());
+    }
+
+    @Test
+    public void testAddEdgePropertyValueSet() {
+        schema().propertyKey("time")
+                .asDate()
+                .valueSet()
+                .ifNotExist()
+                .create();
+        schema().edgeLabel("created")
+                .properties("time")
+                .nullableKeys("time")
+                .append();
+
+        Object peterId = getVertexId("person", "name", "peter");
+        Object lopId = getVertexId("software", "name", "lop");
+
+        Edge created = graph().addEdge(peterId, "created", lopId,
+                                       "date", "2017-03-24",
+                                       "time", "2012-10-10");
+
+        Map<String, Object> props = ImmutableMap.of(
+                                    "date", Utils.formatDate("2017-03-24"),
+                                    "time", ImmutableList.of(
+                                            Utils.formatDate("2012-10-10")));
+        Assert.assertEquals(props, created.properties());
+
+        created.property("time", "2014-02-14");
+        props = ImmutableMap.of("date", Utils.formatDate("2017-03-24"),
+                                "time", ImmutableList.of(
+                                        Utils.formatDate("2012-10-10"),
+                                        Utils.formatDate("2014-02-14")));
+        Assert.assertEquals(props, created.properties());
+    }
+
+    @Test
+    public void testAddEdgePropertyValueListWithSameValue() {
+        schema().propertyKey("time")
+                .asDate()
+                .valueList()
+                .ifNotExist()
+                .create();
+        schema().edgeLabel("created")
+                .properties("time")
+                .nullableKeys("time")
+                .append();
+
+        Object peterId = getVertexId("person", "name", "peter");
+        Object lopId = getVertexId("software", "name", "lop");
+
+        Edge created = graph().addEdge(peterId, "created", lopId,
+                                       "date", "2017-03-24",
+                                       "time", "2012-10-10");
+
+        Map<String, Object> props = ImmutableMap.of(
+                                    "date", Utils.formatDate("2017-03-24"),
+                                    "time", ImmutableList.of(
+                                            Utils.formatDate("2012-10-10")));
+        Assert.assertEquals(props, created.properties());
+
+        created.property("time", "2012-10-10");
+        props = ImmutableMap.of("date", Utils.formatDate("2017-03-24"),
+                                "time", ImmutableList.of(
+                                        Utils.formatDate("2012-10-10"),
+                                        Utils.formatDate("2012-10-10")));
+        Assert.assertEquals(props, created.properties());
+    }
+
+    @Test
+    public void testAddEdgePropertyValueSetWithSameValue() {
+        schema().propertyKey("time")
+                .asDate()
+                .valueSet()
+                .ifNotExist()
+                .create();
+        schema().edgeLabel("created")
+                .properties("time")
+                .nullableKeys("time")
+                .append();
+
+        Object peterId = getVertexId("person", "name", "peter");
+        Object lopId = getVertexId("software", "name", "lop");
+
+        Edge created = graph().addEdge(peterId, "created", lopId,
+                                       "date", "2017-03-24",
+                                       "time", "2012-10-10");
+
+        Map<String, Object> props = ImmutableMap.of(
+                                    "date", Utils.formatDate("2017-03-24"),
+                                    "time", ImmutableList.of(
+                                            Utils.formatDate("2012-10-10")));
+        Assert.assertEquals(props, created.properties());
+
+        created.property("time", "2012-10-10");
+        props = ImmutableMap.of("date", Utils.formatDate("2017-03-24"),
+                                "time", ImmutableList.of(
+                                        Utils.formatDate("2012-10-10")));
+        Assert.assertEquals(props, created.properties());
+    }
+
+    @Test
+    public void testAddEdgeWithMapProperties() {
+        Vertex peter = getVertex("person", "name", "peter");
+        Vertex lop = getVertex("software", "name", "lop");
+
+        Map<String, Object> properties = ImmutableMap.of("date", "2017-03-24",
+                                                         "city", "HongKong");
+        Edge created = graph().addEdge(peter, "created", lop, properties);
+        Map<String, Object> props = ImmutableMap.of(
+                                    "date", Utils.formatDate("2017-03-24"),
+                                    "city", "HongKong");
+        Assert.assertEquals(props, created.properties());
+    }
+
+    @Test
+    public void testRemoveEdgeProperty() {
+        schema().propertyKey("time")
+                .asDate()
+                .valueSet()
+                .ifNotExist()
+                .create();
+        schema().edgeLabel("created")
+                .properties("time")
+                .nullableKeys("time")
+                .append();
+
+        Object peterId = getVertexId("person", "name", "peter");
+        Object lopId = getVertexId("software", "name", "lop");
+
+        Edge created = graph().addEdge(peterId, "created", lopId,
+                                       "date", "2017-03-24",
+                                       "time", "2012-10-10");
+
+        Map<String, Object> props = ImmutableMap.of(
+                                    "date", Utils.formatDate("2017-03-24"),
+                                    "time", ImmutableList.of(
+                                            Utils.formatDate("2012-10-10")));
+        Assert.assertEquals(props, created.properties());
+
+        created.removeProperty("time");
+        props = ImmutableMap.of("date", Utils.formatDate("2017-03-24"));
+        Assert.assertEquals(props, created.properties());
+    }
+
+    @Test
+    public void testRemoveEdgePropertyNotExist() {
+        Object peterId = getVertexId("person", "name", "peter");
+        Object lopId = getVertexId("software", "name", "lop");
+
+        Edge created = graph().addEdge(peterId, "created", lopId,
+                                       "date", "2017-03-24");
+        Map<String, Object> props = ImmutableMap.of(
+                                    "date", Utils.formatDate("2017-03-24"));
+        Assert.assertEquals(props, created.properties());
+
+        Assert.assertThrows(InvalidOperationException.class, () -> {
+            created.removeProperty("not-exist");
+        });
+    }
+
+    @Test
+    public void testName() {
+        BaseClientTest.initEdge();
+
+        Object markoId = getVertexId("person", "name", "marko");
+        List<Edge> edges = graph().getEdges(markoId, Direction.OUT, "knows");
+        Assert.assertEquals(2, edges.size());
+        Edge edge1 = edges.get(0);
+        Edge edge2 = edges.get(1);
+        Date date1 = DateUtil.parse((String) edge1.property("date"));
+        Date date2 = DateUtil.parse((String) edge2.property("date"));
+        String name1 = edge1.name();
+        String name2 = edge2.name();
+        if (date1.before(date2)) {
+            Assert.assertTrue(name1.compareTo(name2) < 0);
+        } else {
+            Assert.assertTrue(name1.compareTo(name2) >= 0);
+        }
+    }
+
+    @Test
+    public void testGetAllEdges() {
+        BaseClientTest.initEdge();
+
+        Object markoId = getVertexId("person", "name", "marko");
+        Object vadasId = getVertexId("person", "name", "vadas");
+        Object joshId = getVertexId("person", "name", "josh");
+        Object peterId = getVertexId("person", "name", "peter");
+        Object lopId = getVertexId("software", "name", "lop");
+        Object rippleId = getVertexId("software", "name", "ripple");
+
+        List<Edge> edges = graph().listEdges();
+        Assert.assertEquals(6, edges.size());
+        assertContains(edges, markoId, "knows", vadasId,
+                       "date", Utils.formatDate("2012-01-10"));
+        assertContains(edges, markoId, "knows", joshId,
+                       "date", Utils.formatDate("2013-01-10"));
+        assertContains(edges, markoId, "created", lopId,
+                       "date", Utils.formatDate("2014-01-10"),
+                       "city", "Shanghai");
+        assertContains(edges, joshId, "created", rippleId,
+                       "date", Utils.formatDate("2015-01-10"),
+                       "city", "Beijing");
+        assertContains(edges, joshId, "created", lopId,
+                       "date", Utils.formatDate("2016-01-10"),
+                       "city", "Beijing");
+        assertContains(edges, peterId, "created", lopId,
+                       "date", Utils.formatDate("2017-01-10"),
+                       "city", "Hongkong");
+    }
+
+    @Test
+    public void testGetAllEdgesWithNoLimit() {
+        BaseClientTest.initEdge();
+
+        Object markoId = getVertexId("person", "name", "marko");
+        Object vadasId = getVertexId("person", "name", "vadas");
+        Object joshId = getVertexId("person", "name", "josh");
+        Object peterId = getVertexId("person", "name", "peter");
+        Object lopId = getVertexId("software", "name", "lop");
+        Object rippleId = getVertexId("software", "name", "ripple");
+
+        List<Edge> edges = graph().listEdges(-1);
+        Assert.assertEquals(6, edges.size());
+        assertContains(edges, markoId, "knows", vadasId,
+                       "date", Utils.formatDate("2012-01-10"));
+        assertContains(edges, markoId, "knows", joshId,
+                       "date", Utils.formatDate("2013-01-10"));
+        assertContains(edges, markoId, "created", lopId,
+                       "date", Utils.formatDate("2014-01-10"),
+                       "city", "Shanghai");
+        assertContains(edges, joshId, "created", rippleId,
+                       "date", Utils.formatDate("2015-01-10"),
+                       "city", "Beijing");
+        assertContains(edges, joshId, "created", lopId,
+                       "date", Utils.formatDate("2016-01-10"),
+                       "city", "Beijing");
+        assertContains(edges, peterId, "created", lopId,
+                       "date", Utils.formatDate("2017-01-10"),
+                       "city", "Hongkong");
+    }
+
+    @Test
+    public void testGetEdgesByVertexId() {
+        BaseClientTest.initEdge();
+
+        Object markoId = getVertexId("person", "name", "marko");
+        Object vadasId = getVertexId("person", "name", "vadas");
+        Object joshId = getVertexId("person", "name", "josh");
+        Object lopId = getVertexId("software", "name", "lop");
+
+        List<Edge> edges = graph().getEdges(markoId);
+        Assert.assertEquals(3, edges.size());
+        assertContains(edges, markoId, "knows", vadasId,
+                       "date", Utils.formatDate("2012-01-10"));
+        assertContains(edges, markoId, "knows", joshId,
+                       "date", Utils.formatDate("2013-01-10"));
+        assertContains(edges, markoId, "created", lopId,
+                       "date", Utils.formatDate("2014-01-10"),
+                       "city", "Shanghai");
+    }
+
+    @Test
+    public void testGetEdgesByVertexIdWithLimit2() {
+        BaseClientTest.initEdge();
+
+        Object markoId = getVertexId("person", "name", "marko");
+
+        List<Edge> edges = graph().getEdges(markoId, 2);
+        Assert.assertEquals(2, edges.size());
+        for (Edge edge : edges) {
+            Assert.assertEquals(markoId, edge.sourceId());
+        }
+    }
+
+    @Test
+    public void testGetEdgesByVertexIdDirection() {
+        BaseClientTest.initEdge();
+
+        Object markoId = getVertexId("person", "name", "marko");
+        Object joshId = getVertexId("person", "name", "josh");
+        Object lopId = getVertexId("software", "name", "lop");
+        Object rippleId = getVertexId("software", "name", "ripple");
+
+        List<Edge> edges = graph().getEdges(joshId, Direction.OUT);
+        Assert.assertEquals(2, edges.size());
+        assertContains(edges, joshId, "created", rippleId,
+                       "date", Utils.formatDate("2015-01-10"),
+                       "city", "Beijing");
+        assertContains(edges, joshId, "created", lopId,
+                       "date", Utils.formatDate("2016-01-10"),
+                       "city", "Beijing");
+
+        edges = graph().getEdges(joshId, Direction.IN);
+        Assert.assertEquals(1, edges.size());
+        assertContains(edges, markoId, "knows", joshId,
+                       "date", Utils.formatDate("2013-01-10"));
+    }
+
+    @Test
+    public void testGetEdgesByVertexIdDirectionWithLimit1() {
+        BaseClientTest.initEdge();
+
+        Object joshId = getVertexId("person", "name", "josh");
+
+        List<Edge> edges = graph().getEdges(joshId, Direction.OUT, 1);
+        Assert.assertEquals(1, edges.size());
+        for (Edge edge : edges) {
+            // TODO: Whether need to add direction property in Edge?
+            Assert.assertEquals(joshId, edge.sourceId());
+        }
+
+        edges = graph().getEdges(joshId, Direction.IN, 1);
+        Assert.assertEquals(1, edges.size());
+        for (Edge edge : edges) {
+            Assert.assertEquals(joshId, edge.targetId());
+        }
+    }
+
+    @Test
+    public void testGetEdgesByVertexIdDirectionLabel() {
+        BaseClientTest.initEdge();
+
+        Object markoId = getVertexId("person", "name", "marko");
+        Object joshId = getVertexId("person", "name", "josh");
+        Object lopId = getVertexId("software", "name", "lop");
+        Object rippleId = getVertexId("software", "name", "ripple");
+
+        List<Edge> edges = graph().getEdges(joshId, Direction.OUT,
+                                            "created");
+        Assert.assertEquals(2, edges.size());
+        assertContains(edges, joshId, "created", rippleId,
+                       "date", Utils.formatDate("2015-01-10"),
+                       "city", "Beijing");
+        assertContains(edges, joshId, "created", lopId,
+                       "date", Utils.formatDate("2016-01-10"),
+                       "city", "Beijing");
+
+        edges = graph().getEdges(joshId, Direction.IN, "knows");
+        Assert.assertEquals(1, edges.size());
+        assertContains(edges, markoId, "knows", joshId,
+                       "date", Utils.formatDate("2013-01-10"));
+    }
+
+    @Test
+    public void testGetEdgesByVertexIdDirectionLabelWithLimit1() {
+        BaseClientTest.initEdge();
+
+        Object joshId = getVertexId("person", "name", "josh");
+
+        List<Edge> edges = graph().getEdges(joshId, Direction.OUT,
+                                            "created", 1);
+        Assert.assertEquals(1, edges.size());
+        for (Edge edge : edges) {
+            Assert.assertEquals(joshId, edge.sourceId());
+            Assert.assertEquals("created", edge.label());
+        }
+
+        edges = graph().getEdges(joshId, Direction.IN, "knows", 1);
+        Assert.assertEquals(1, edges.size());
+        for (Edge edge : edges) {
+            Assert.assertEquals(joshId, edge.targetId());
+            Assert.assertEquals("knows", edge.label());
+        }
+    }
+
+    @Test
+    public void testGetEdgesByVertexIdDirectionLabelProperties() {
+        BaseClientTest.initEdge();
+
+        Object markoId = getVertexId("person", "name", "marko");
+        Object joshId = getVertexId("person", "name", "josh");
+        Object rippleId = getVertexId("software", "name", "ripple");
+
+        Map<String, Object> properties = ImmutableMap.of(
+                                         "date",
+                                         Utils.formatDate("2015-01-10"));
+        List<Edge> edges = graph().getEdges(joshId, Direction.OUT,
+                                            "created", properties);
+        Assert.assertEquals(1, edges.size());
+        assertContains(edges, joshId, "created", rippleId,
+                       "date", Utils.formatDate("2015-01-10"),
+                       "city", "Beijing");
+
+        properties = ImmutableMap.of("date", Utils.formatDate("2013-01-10"));
+        edges = graph().getEdges(joshId, Direction.IN, "knows", properties);
+        Assert.assertEquals(1, edges.size());
+        assertContains(edges, markoId, "knows", joshId,
+                       "date", Utils.formatDate("2013-01-10"));
+    }
+
+    @Test
+    public void testGetEdgesByVertexIdDirectionLabelPropertiesWithLimit1() {
+        BaseClientTest.initEdge();
+
+        Object joshId = getVertexId("person", "name", "josh");
+
+        Map<String, Object> properties = ImmutableMap.of(
+                                         "date",
+                                         Utils.formatDate("2015-01-10"));
+        List<Edge> edges = graph().getEdges(joshId, Direction.OUT,
+                                            "created", properties);
+        Assert.assertEquals(1, edges.size());
+        for (Edge edge : edges) {
+            Assert.assertEquals(joshId, edge.sourceId());
+            Assert.assertEquals("created", edge.label());
+        }
+
+        properties = ImmutableMap.of("date", Utils.formatDate("2013-01-10"));
+        edges = graph().getEdges(joshId, Direction.IN, "knows", properties);
+        Assert.assertEquals(1, edges.size());
+        for (Edge edge : edges) {
+            Assert.assertEquals(joshId, edge.targetId());
+            Assert.assertEquals("knows", edge.label());
+        }
+    }
+
+    @Test
+    public void testGetEdgesByLabelAndPropertiesWithRangeCondition()
+                throws ParseException {
+        schema().indexLabel("knowsByDate").range()
+                .onE("knows").by("date").create();
+        schema().indexLabel("createdByDate").range()
+                .onE("created").by("date").create();
+
+        BaseClientTest.initEdge();
+
+        Date expected = DateUtil.parse("2014-01-10");
+        Date expected2 = DateUtil.parse("2016-01-10");
+
+        Map<String, Object> properties = ImmutableMap.of(
+                                         "date", "P.eq(\"2014-1-10\")");
+        List<Edge> edges = graph().listEdges("created", properties);
+
+        Date time;
+        Assert.assertEquals(1, edges.size());
+        for (Edge e : edges) {
+            Assert.assertEquals("created", e.label());
+            time = DateUtil.parse((String) e.property("date"));
+            Assert.assertEquals(expected.getTime(), time.getTime());
+        }
+
+        properties = ImmutableMap.of("date", "P.gt(\"2014-1-10\")");
+        edges = graph().listEdges("created", properties);
+        Assert.assertEquals(3, edges.size());
+        for (Edge e : edges) {
+            Assert.assertEquals("created", e.label());
+            time = DateUtil.parse((String) e.property("date"));
+            Assert.assertGt(expected.getTime(), time.getTime());
+        }
+
+        properties = ImmutableMap.of("date", "P.gte(\"2014-1-10\")");
+        edges = graph().listEdges("created", properties);
+        Assert.assertEquals(4, edges.size());
+        for (Edge e : edges) {
+            Assert.assertEquals("created", e.label());
+            time = DateUtil.parse((String) e.property("date"));
+            Assert.assertGte(expected.getTime(), time.getTime());
+        }
+
+        properties = ImmutableMap.of("date", "P.lt(\"2014-1-10\")");
+        edges = graph().listEdges(null, properties);
+        Assert.assertEquals(2, edges.size());
+        for (Edge e : edges) {
+            Assert.assertEquals("knows", e.label());
+            time = DateUtil.parse((String) e.property("date"));
+            Assert.assertLt(expected.getTime(), time.getTime());
+        }
+
+        properties = ImmutableMap.of("date", "P.lte(\"2014-1-10\")");
+        edges = graph().listEdges(null, properties);
+        Assert.assertEquals(3, edges.size());
+        for (Edge e : edges) {
+            time = DateUtil.parse((String) e.property("date"));
+            Assert.assertLte(expected.getTime(), time.getTime());
+        }
+
+        properties = ImmutableMap.of("date",
+                                     "P.between(\"2014-1-10\",\"2016-1-10\")");
+        edges = graph().listEdges(null, properties);
+        Assert.assertEquals(2, edges.size());
+        for (Edge e : edges) {
+            Assert.assertEquals("created", e.label());
+            time = DateUtil.parse((String) e.property("date"));
+            Assert.assertGte(expected.getTime(), time.getTime());
+            Assert.assertLt(expected2.getTime(), time.getTime());
+        }
+
+        properties = ImmutableMap.of("date",
+                                     "P.inside(\"2014-1-10\",\"2016-1-10\")");
+        edges = graph().listEdges(null, properties);
+        Assert.assertEquals(1, edges.size());
+        for (Edge e : edges) {
+            Assert.assertEquals("created", e.label());
+            time = DateUtil.parse((String) e.property("date"));
+            Assert.assertGt(expected.getTime(), time.getTime());
+            Assert.assertLt(expected2.getTime(), time.getTime());
+        }
+
+        properties = ImmutableMap.of("date",
+                                     "P.within(\"2014-1-10\",\"2016-1-10\")");
+        edges = graph().listEdges(null, properties);
+        Assert.assertEquals(2, edges.size());
+        for (Edge e : edges) {
+            Assert.assertEquals("created", e.label());
+            time = DateUtil.parse((String) e.property("date"));
+            Assert.assertGte(expected.getTime(), time.getTime());
+            Assert.assertLte(expected2.getTime(), time.getTime());
+        }
+    }
+
+    @Test
+    public void testGetEdgesByLabelAndPropertiesWithKeepP()
+                throws ParseException {
+        schema().indexLabel("createdByCity").secondary()
+                .onE("created").by("city").create();
+        schema().indexLabel("createdByDate").secondary()
+                .onE("created").by("date").create();
+
+        BaseClientTest.initEdge();
+
+        Map<String, Object> properties = ImmutableMap.of(
+                                         "date", "P.eq(\"2014-1-10\")");
+        List<Edge> edges = graph().listEdges("created", properties, false);
+        Assert.assertEquals(1, edges.size());
+
+        Assert.assertThrows(ServerException.class, () -> {
+            graph().listEdges("created", properties, true);
+        }, e -> {
+            Assert.assertContains("Expected date format is:", e.getMessage());
+        });
+
+        Map<String, Object> properties2 = ImmutableMap.of("city", "P.gt(1)");
+        edges = graph().listEdges("created", properties2, true);
+        Assert.assertEquals(0, edges.size());
+
+        edges = graph().listEdges("created", properties2, true, 3);
+        Assert.assertEquals(0, edges.size());
+    }
+
+    @Test
+    public void testIterateEdgesByLabel() {
+        BaseClientTest.initEdge();
+
+        Iterator<Edge> edges = graph().iterateEdges("created", 1);
+        Assert.assertEquals(4, Iterators.size(edges));
+
+        edges = graph().iterateEdges("knows", 1);
+        Assert.assertEquals(2, Iterators.size(edges));
+    }
+
+    @Test
+    public void testIterateEdgesByVertexId() {
+        BaseClientTest.initEdge();
+
+        Object markoId = getVertexId("person", "name", "marko");
+
+        Iterator<Edge> edges = graph().iterateEdges(markoId, 1);
+        Assert.assertEquals(3, Iterators.size(edges));
+
+        edges = graph().iterateEdges(markoId, Direction.OUT, 1);
+        Assert.assertEquals(3, Iterators.size(edges));
+
+        edges = graph().iterateEdges(markoId, Direction.OUT, "knows", 1);
+        Assert.assertEquals(2, Iterators.size(edges));
+
+        edges = graph().iterateEdges(markoId, Direction.OUT, "created", 1);
+        Assert.assertEquals(1, Iterators.size(edges));
+
+        Map<String, Object> properties = ImmutableMap.of("date",
+                                                         "P.gt(\"2012-1-1\")");
+        Iterator<Edge> iter = graph().iterateEdges(markoId, Direction.OUT,
+                                                   "knows", properties, 1);
+        Assert.assertEquals(2, Iterators.size(iter));
+    }
+
+    @Test
+    public void testQueryByPagingAndFiltering() {
+        SchemaManager schema = schema();
+        schema.propertyKey("no").asText().create();
+        schema.propertyKey("location").asText().create();
+        schema.propertyKey("callType").asText().create();
+        schema.propertyKey("calltime").asDate().create();
+        schema.propertyKey("duration").asInt().create();
+        schema.vertexLabel("phone")
+              .properties("no")
+              .primaryKeys("no")
+              .enableLabelIndex(false)
+              .create();
+        schema.edgeLabel("call").multiTimes()
+              .properties("location", "callType", "duration", "calltime")
+              .sourceLabel("phone").targetLabel("phone")
+              .sortKeys("location", "callType", "duration", "calltime")
+              .create();
+
+        Vertex v1 = graph().addVertex(T.label, "phone", "no", "13812345678");
+        Vertex v2 = graph().addVertex(T.label, "phone", "no", "13866668888");
+        Vertex v10086 = graph().addVertex(T.label, "phone", "no", "10086");
+
+        v1.addEdge("call", v2, "location", "Beijing", "callType", "work",
+                   "duration", 3, "calltime", "2017-5-1 23:00:00");
+        v1.addEdge("call", v2, "location", "Beijing", "callType", "work",
+                   "duration", 3, "calltime", "2017-5-2 12:00:01");
+        v1.addEdge("call", v2, "location", "Beijing", "callType", "work",
+                   "duration", 3, "calltime", "2017-5-3 12:08:02");
+        v1.addEdge("call", v2, "location", "Beijing", "callType", "work",
+                   "duration", 8, "calltime", "2017-5-3 22:22:03");
+        v1.addEdge("call", v2, "location", "Beijing", "callType", "fun",
+                   "duration", 10, "calltime", "2017-5-4 20:33:04");
+
+        v1.addEdge("call", v10086, "location", "Nanjing", "callType", "work",
+                   "duration", 12, "calltime", "2017-5-2 15:30:05");
+        v1.addEdge("call", v10086, "location", "Nanjing", "callType", "work",
+                   "duration", 14, "calltime", "2017-5-3 14:56:06");
+        v2.addEdge("call", v10086, "location", "Nanjing", "callType", "fun",
+                   "duration", 15, "calltime", "2017-5-3 17:28:07");
+
+        ResultSet resultSet = gremlin().gremlin("g.V(vid).outE('call')" +
+                                                ".has('location', 'Beijing')" +
+                                                ".has('callType', 'work')" +
+                                                ".has('duration', 3)" +
+                                                ".has('calltime', " +
+                                                "P.between('2017-5-2', " +
+                                                "'2017-5-4'))" +
+                                                ".toList()")
+                                       .binding("vid", v1.id())
+                                       .execute();
+        Iterator<Result> results = resultSet.iterator();
+        Assert.assertEquals(2, Iterators.size(results));
+
+        Assert.assertThrows(ServerException.class, () -> {
+            // no location
+            gremlin().gremlin("g.V(vid).outE('call').has('callType', 'work')" +
+                              ".has('duration', 3).has('calltime', " +
+                              "P.between('2017-5-2', '2017-5-4'))" +
+                              ".has('~page', '')")
+                     .binding("vid", v1.id())
+                     .execute();
+        }, e -> {
+            Assert.assertContains("Can't query by paging and filtering",
+                                  e.getMessage());
+        });
+    }
+
+    private static void assertContains(List<Edge> edges, Object source,
+                                       String label, Object target,
+                                       Object... keyValues) {
+        Map<String, Object> properties = Utils.asMap(keyValues);
+
+        Edge edge = new Edge(label);
+        edge.sourceId(source);
+        edge.targetId(target);
+        for (String key : properties.keySet()) {
+            edge.property(key, properties.get(key));
+        }
+
+        Assert.assertTrue(Utils.contains(edges, edge));
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/functional/FuncTestSuite.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/functional/FuncTestSuite.java
new file mode 100644
index 0000000..05351f9
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/functional/FuncTestSuite.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.functional;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+@RunWith(Suite.class)
+@Suite.SuiteClasses({
+    PropertyKeyTest.class,
+    VertexLabelTest.class,
+    EdgeLabelTest.class,
+    IndexLabelTest.class,
+    SchemaTest.class,
+    VertexTest.class,
+    EdgeTest.class,
+    BatchInsertTest.class,
+    GraphManagerTest.class,
+    AuthManagerTest.class,
+    TraverserManagerTest.class,
+    MetricsManagerTest.class,
+    HugeClientHttpsTest.class,
+    HugeClientTest.class
+})
+public class FuncTestSuite {
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/functional/GraphManagerTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/functional/GraphManagerTest.java
new file mode 100644
index 0000000..f421aec
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/functional/GraphManagerTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.functional;
+
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.commons.collections.IteratorUtils;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.baidu.hugegraph.BaseClientTest;
+import com.baidu.hugegraph.structure.graph.Edge;
+import com.baidu.hugegraph.structure.graph.Vertex;
+import com.baidu.hugegraph.testutil.Assert;
+
+public class GraphManagerTest extends BaseFuncTest {
+
+    @Before
+    public void setup() {
+        BaseClientTest.initPropertyKey();
+        BaseClientTest.initVertexLabel();
+        BaseClientTest.initEdgeLabel();
+        BaseClientTest.initVertex();
+        BaseClientTest.initEdge();
+    }
+
+    @After
+    public void teardown() throws Exception {
+        BaseFuncTest.clearData();
+    }
+
+    @Test
+    @SuppressWarnings("unchecked")
+    public void testIterateVertices() {
+        Iterator<Vertex> vertices = graph().iterateVertices(1);
+        List<Vertex> results = IteratorUtils.toList(vertices);
+        Assert.assertEquals(6, results.size());
+
+        vertices = graph().iterateVertices(6);
+        results = IteratorUtils.toList(vertices);
+        Assert.assertEquals(6, results.size());
+
+        vertices = graph().iterateVertices(100);
+        results = IteratorUtils.toList(vertices);
+        Assert.assertEquals(6, results.size());
+    }
+
+    @Test
+    @SuppressWarnings("unchecked")
+    public void testIterateEdgesWithPageLtThanTotal() {
+        Iterator<Edge> edes = graph().iterateEdges(1);
+        List<Edge> results = IteratorUtils.toList(edes);
+        Assert.assertEquals(6, results.size());
+
+        edes = graph().iterateEdges(6);
+        results = IteratorUtils.toList(edes);
+        Assert.assertEquals(6, results.size());
+
+        edes = graph().iterateEdges(100);
+        results = IteratorUtils.toList(edes);
+        Assert.assertEquals(6, results.size());
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/functional/GraphModeTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/functional/GraphModeTest.java
new file mode 100644
index 0000000..2182aa6
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/functional/GraphModeTest.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.functional;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.baidu.hugegraph.driver.GraphsManager;
+import com.baidu.hugegraph.structure.constant.GraphMode;
+import com.baidu.hugegraph.testutil.Assert;
+
+public class GraphModeTest extends BaseFuncTest {
+
+    @Before
+    public void setup() {
+        graphs().mode(GRAPH, GraphMode.NONE);
+    }
+
+    @After
+    public void teardown() throws Exception {
+        graphs().mode(GRAPH, GraphMode.NONE);
+    }
+
+    @Test
+    public void testGetGraphMode() {
+        GraphsManager graphs = graphs();
+        Assert.assertEquals(GraphMode.NONE, graphs.mode(GRAPH));
+    }
+
+    @Test
+    public void testSetGraphMode() {
+        GraphsManager graphs = graphs();
+        graphs.mode(GRAPH, GraphMode.MERGING);
+        Assert.assertEquals(GraphMode.MERGING, graphs.mode(GRAPH));
+        graphs.mode(GRAPH, GraphMode.RESTORING);
+        Assert.assertEquals(GraphMode.RESTORING, graphs.mode(GRAPH));
+        graphs.mode(GRAPH, GraphMode.RESTORING);
+        Assert.assertEquals(GraphMode.RESTORING, graphs.mode(GRAPH));
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/functional/HugeClientHttpsTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/functional/HugeClientHttpsTest.java
new file mode 100644
index 0000000..a3263eb
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/functional/HugeClientHttpsTest.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.functional;
+
+import java.util.Map;
+
+import org.junit.After;
+import org.junit.Test;
+
+import com.baidu.hugegraph.driver.GraphManager;
+import com.baidu.hugegraph.driver.HugeClient;
+import com.baidu.hugegraph.driver.SchemaManager;
+import com.baidu.hugegraph.structure.constant.T;
+import com.baidu.hugegraph.structure.graph.Vertex;
+import com.baidu.hugegraph.testutil.Assert;
+import com.google.common.collect.ImmutableMap;
+
+public class HugeClientHttpsTest {
+
+    private static final String BASE_URL = "https://127.0.0.1:8443";
+    private static final String GRAPH = "hugegraph";
+    private static final String USERNAME = "admin";
+    private static final String PASSWORD = "pa";
+    private static final int TIMEOUT = 10;
+    private static final int MAX_CONNS_PER_ROUTE = 10;
+    private static final int MAX_CONNS = 10;
+    private static final int IDLE_TIME = 30;
+    private static final String TRUST_STORE_FILE =
+                                "src/test/resources/hugegraph.truststore";
+    private static final String TRUST_STORE_PASSWORD = "hugegraph";
+
+    private static HugeClient client;
+
+    @After
+    public void teardown() throws Exception {
+        Assert.assertNotNull("Client is not opened", client);
+        client.close();
+    }
+
+    @Test
+    public void testHttpsClientBuilderWithConnection() {
+        client = HugeClient.builder(BASE_URL, GRAPH)
+                           .configUser(USERNAME, PASSWORD)
+                           .configSSL(TRUST_STORE_FILE, TRUST_STORE_PASSWORD)
+                           .build();
+        Assert.assertTrue(client.graphs().listGraph().contains("hugegraph"));
+        this.addVertexAndCheckPropertyValue();
+    }
+
+    @Test
+    public void testHttpsClientWithConnectionPoolNoUserParam() {
+        client = HugeClient.builder(BASE_URL, GRAPH)
+                           .configTimeout(TIMEOUT)
+                           .configPool(MAX_CONNS, MAX_CONNS_PER_ROUTE)
+                           .configSSL(TRUST_STORE_FILE, TRUST_STORE_PASSWORD)
+                           .build();
+        Assert.assertTrue(client.graphs().listGraph().contains("hugegraph"));
+        this.addVertexAndCheckPropertyValue();
+    }
+
+    @Test
+    public void testHttpsClientWithConnectionPoolNoTimeOutParam() {
+        client = HugeClient.builder(BASE_URL, GRAPH)
+                           .configUser(USERNAME, PASSWORD)
+                           .configPool(MAX_CONNS, MAX_CONNS_PER_ROUTE)
+                           .configSSL(TRUST_STORE_FILE, TRUST_STORE_PASSWORD)
+                           .build();
+        Assert.assertTrue(client.graphs().listGraph().contains("hugegraph"));
+        this.addVertexAndCheckPropertyValue();
+    }
+
+    @Test
+    public void testHttpsClientNewBuilderWithConnectionNoPoolParam() {
+        client = HugeClient.builder(BASE_URL, GRAPH)
+                           .configUser(USERNAME, PASSWORD)
+                           .configTimeout(TIMEOUT)
+                           .configSSL(TRUST_STORE_FILE, TRUST_STORE_PASSWORD)
+                           .build();
+        Assert.assertTrue(client.graphs().listGraph().contains("hugegraph"));
+        this.addVertexAndCheckPropertyValue();
+    }
+
+    @Test
+    public void testHttpsClientNewBuilderWithConnectionPool() {
+        client = HugeClient.builder(BASE_URL, GRAPH)
+                           .configUser(USERNAME, PASSWORD)
+                           .configTimeout(TIMEOUT)
+                           .configPool(MAX_CONNS, MAX_CONNS_PER_ROUTE)
+                           .configSSL(TRUST_STORE_FILE, TRUST_STORE_PASSWORD)
+                           .configIdleTime(IDLE_TIME)
+                           .build();
+        Assert.assertTrue(client.graphs().listGraph().contains("hugegraph"));
+        this.addVertexAndCheckPropertyValue();
+    }
+
+    @Test
+    public void testHttpsClientNewBuilderZeroPoolParam() {
+        client = HugeClient.builder(BASE_URL, GRAPH)
+                           .configUser(USERNAME, PASSWORD)
+                           .configTimeout(TIMEOUT)
+                           .configPool(0, 0)
+                           .configSSL(TRUST_STORE_FILE, TRUST_STORE_PASSWORD)
+                           .build();
+        Assert.assertTrue(client.graphs().listGraph().contains("hugegraph"));
+        this.addVertexAndCheckPropertyValue();
+    }
+
+    @Test
+    public void testHttpsClientBuilderWithConnectionPoolNoParam() {
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            HugeClient.builder(BASE_URL, GRAPH)
+                      .configUrl(null)
+                      .configGraph(null)
+                      .configSSL("", "")
+                      .build();
+        }, e -> {
+            Assert.assertContains("The url parameter can't be null",
+                                  e.getMessage());
+        });
+    }
+
+    @Test
+    public void testHttpsClientBuilderWithConnectionPoolNoGraphParam() {
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            HugeClient.builder(BASE_URL, GRAPH)
+                      .configGraph(null)
+                      .configSSL("", "")
+                      .build();
+        }, e -> {
+            Assert.assertContains("The graph parameter can't be null",
+                                  e.getMessage());
+        });
+    }
+
+    @Test
+    public void testHttpsClientBuilderWithConnectionPoolZeroIdleTimeParam() {
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            HugeClient.builder(BASE_URL, GRAPH)
+                      .configIdleTime(0)
+                      .build();
+        }, e -> {
+            Assert.assertContains("The idleTime parameter must be > 0, but got",
+                                  e.getMessage());
+        });
+    }
+
+    private void addVertexAndCheckPropertyValue() {
+        SchemaManager schema = client.schema();
+        schema.propertyKey("name").asText().ifNotExist().create();
+        schema.propertyKey("age").asInt().ifNotExist().create();
+        schema.propertyKey("city").asText().ifNotExist().create();
+        schema.vertexLabel("person")
+              .properties("name", "age", "city")
+              .primaryKeys("name")
+              .ifNotExist()
+              .create();
+        GraphManager graph = client.graph();
+        Vertex marko = graph.addVertex(T.label, "person", "name", "marko",
+                                       "age", 29, "city", "Beijing");
+        Map<String, Object> props = ImmutableMap.of("name", "marko",
+                                                    "age", 29,
+                                                    "city", "Beijing");
+        Assert.assertEquals(props, marko.properties());
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/functional/HugeClientTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/functional/HugeClientTest.java
new file mode 100644
index 0000000..06086ac
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/functional/HugeClientTest.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.functional;
+
+import org.junit.Test;
+
+import com.baidu.hugegraph.driver.HugeClient;
+import com.baidu.hugegraph.testutil.Assert;
+
+public class HugeClientTest {
+
+    protected static final String BASE_URL = "http://127.0.0.1:8080";
+    protected static final String GRAPH = "hugegraph";
+    protected static final String USERNAME = "admin";
+    protected static final String PASSWORD = "pa";
+
+    @Test
+    public void testContext() {
+        HugeClient client = HugeClient.builder(BASE_URL, GRAPH)
+                                      .configUser(USERNAME, PASSWORD)
+                                      .build();
+
+        String token = "Bearer token";
+        client.setAuthContext(token);
+        Assert.assertEquals(token, client.getAuthContext());
+
+        client.resetAuthContext();
+        Assert.assertNull(client.getAuthContext());
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/functional/IndexLabelTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/functional/IndexLabelTest.java
new file mode 100644
index 0000000..a9192ea
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/functional/IndexLabelTest.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.functional;
+
+import java.util.Date;
+import java.util.List;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.baidu.hugegraph.driver.SchemaManager;
+import com.baidu.hugegraph.structure.schema.IndexLabel;
+import com.baidu.hugegraph.testutil.Assert;
+import com.baidu.hugegraph.util.DateUtil;
+import com.google.common.collect.ImmutableList;
+
+public class IndexLabelTest extends BaseFuncTest {
+
+    @Before
+    public void setup() {
+        BaseFuncTest.initPropertyKey();
+    }
+
+    @After
+    public void teardown() throws Exception {
+        BaseFuncTest.clearData();
+    }
+
+    @Test
+    public void testAddIndexLabelWithUserData() {
+        SchemaManager schema = schema();
+        BaseFuncTest.initVertexLabel();
+
+        IndexLabel personByCity = schema.indexLabel("personByCity")
+                                        .onV("person")
+                                        .by("city")
+                                        .secondary()
+                                        .userdata("type", "secondary")
+                                        .ifNotExist()
+                                        .create();
+        Assert.assertEquals(2, personByCity.userdata().size());
+        Assert.assertEquals("secondary", personByCity.userdata().get("type"));
+        String time = (String) personByCity.userdata().get("~create_time");
+        Date createTime = DateUtil.parse(time);
+        Assert.assertTrue(createTime.before(DateUtil.now()));
+
+        IndexLabel personByAge = schema.indexLabel("personByAge")
+                                       .onV("person")
+                                       .by("age")
+                                       .range()
+                                       .userdata("type", "secondary")
+                                       .userdata("type", "range")
+                                       .ifNotExist()
+                                       .create();
+        Assert.assertEquals(2, personByAge.userdata().size());
+        Assert.assertEquals("range", personByAge.userdata().get("type"));
+        time = (String) personByAge.userdata().get("~create_time");
+        createTime = DateUtil.parse(time);
+        Assert.assertTrue(createTime.before(DateUtil.now()));
+    }
+
+    @Test
+    public void testAppendIndexLabelWithUserData() {
+        SchemaManager schema = schema();
+        BaseFuncTest.initVertexLabel();
+
+        IndexLabel personByCity = schema.indexLabel("personByCity")
+                                        .onV("person")
+                                        .by("city")
+                                        .secondary()
+                                        .ifNotExist()
+                                        .create();
+        Assert.assertEquals(1, personByCity.userdata().size());
+        String time = (String) personByCity.userdata().get("~create_time");
+        Date createTime = DateUtil.parse(time);
+        Assert.assertTrue(createTime.before(DateUtil.now()));
+
+        personByCity = schema.indexLabel("personByCity")
+                             .userdata("type", "secondary")
+                             .append();
+        Assert.assertEquals(2, personByCity.userdata().size());
+        Assert.assertEquals("secondary", personByCity.userdata().get("type"));
+        time = (String) personByCity.userdata().get("~create_time");
+        Assert.assertEquals(createTime, DateUtil.parse(time));
+    }
+
+    @Test
+    public void testEliminateIndexLabelWithUserData() {
+        SchemaManager schema = schema();
+        BaseFuncTest.initVertexLabel();
+
+        IndexLabel personByCity = schema.indexLabel("personByCity")
+                                        .onV("person")
+                                        .by("city")
+                                        .secondary()
+                                        .userdata("type", "secondary")
+                                        .userdata("icon", "picture")
+                                        .ifNotExist()
+                                        .create();
+        Assert.assertEquals(3, personByCity.userdata().size());
+        Assert.assertEquals("secondary", personByCity.userdata().get("type"));
+        Assert.assertEquals("picture", personByCity.userdata().get("icon"));
+        String time = (String) personByCity.userdata().get("~create_time");
+        Date createTime = DateUtil.parse(time);
+        Assert.assertTrue(createTime.before(DateUtil.now()));
+
+        personByCity = schema.indexLabel("personByCity")
+                             .userdata("type", "secondary")
+                             .eliminate();
+        Assert.assertEquals(2, personByCity.userdata().size());
+        Assert.assertEquals("picture", personByCity.userdata().get("icon"));
+        time = (String) personByCity.userdata().get("~create_time");
+        Assert.assertEquals(createTime, DateUtil.parse(time));
+    }
+
+    @Test
+    public void testRemoveIndexLabelSync() {
+        SchemaManager schema = schema();
+
+        schema.vertexLabel("player")
+              .properties("name", "age")
+              .create();
+        IndexLabel playerByName = schema.indexLabel("playerByName")
+                                        .on(true, "player")
+                                        .secondary()
+                                        .by("name")
+                                        .create();
+
+        Assert.assertNotNull(playerByName);
+        // Remove index label sync
+        schema.removeIndexLabel("playerByName");
+
+        playerByName = schema.indexLabel("playerByName")
+                             .onV("player")
+                             .by("name")
+                             .secondary()
+                             .create();
+
+        Assert.assertNotNull(playerByName);
+        // Remove index label sync with timeout
+        schema.removeIndexLabel("playerByName", 10);
+    }
+
+    @Test
+    public void testRemoveIndexLabelAsync() {
+        SchemaManager schema = schema();
+
+        schema.vertexLabel("player")
+              .properties("name", "age")
+              .create();
+        IndexLabel playerByName = schema.indexLabel("playerByName")
+                                        .onV("player")
+                                        .by("name")
+                                        .secondary()
+                                        .create();
+        Assert.assertNotNull(playerByName);
+        // Remove index label async
+        schema.removeIndexLabelAsync("playerByName");
+    }
+
+    @Test
+    public void testAddIndexLabelAsync() {
+        SchemaManager schema = schema();
+
+        schema.vertexLabel("player")
+              .properties("name", "age")
+              .create();
+        IndexLabel playerByName = schema.indexLabel("playerByName")
+                                        .onV("player")
+                                        .by("name")
+                                        .secondary()
+                                        .build();
+        long task = schema.addIndexLabelAsync(playerByName);
+        waitUntilTaskCompleted(task);
+
+        playerByName = schema.getIndexLabel(playerByName.name());
+        Assert.assertNotNull(playerByName);
+    }
+
+    @Test
+    public void testListByNames() {
+        SchemaManager schema = schema();
+
+        schema.vertexLabel("player").properties("name", "age").create();
+        IndexLabel playerByName = schema.indexLabel("playerByName")
+                                        .onV("player")
+                                        .by("name")
+                                        .secondary()
+                                        .create();
+        IndexLabel playerByAge = schema.indexLabel("playerByAge")
+                                       .onV("player")
+                                       .by("age")
+                                       .range()
+                                       .create();
+
+        List<IndexLabel> indexLabels;
+
+        indexLabels = schema.getIndexLabels(ImmutableList.of("playerByName"));
+        Assert.assertEquals(1, indexLabels.size());
+        assertContains(indexLabels, playerByName);
+
+        indexLabels = schema.getIndexLabels(ImmutableList.of("playerByAge"));
+        Assert.assertEquals(1, indexLabels.size());
+        assertContains(indexLabels, playerByAge);
+
+        indexLabels = schema.getIndexLabels(ImmutableList.of("playerByName",
+                                                             "playerByAge"));
+        Assert.assertEquals(2, indexLabels.size());
+        assertContains(indexLabels, playerByName);
+        assertContains(indexLabels, playerByAge);
+    }
+
+    @Test
+    public void testResetVertexLabelId() {
+        SchemaManager schema = schema();
+        schema.vertexLabel("player")
+              .properties("name", "age")
+              .create();
+        IndexLabel playerByName = schema.indexLabel("playerByName")
+                                        .onV("player")
+                                        .by("name")
+                                        .secondary()
+                                        .create();
+        Assert.assertTrue(playerByName.id() > 0);
+        playerByName.resetId();
+        Assert.assertEquals(0L, playerByName.id());
+    }
+
+    @Test
+    public void testSetCheckExist() {
+        SchemaManager schema = schema();
+        schema.vertexLabel("player")
+              .properties("name", "age")
+              .create();
+        IndexLabel playerByName = schema.indexLabel("playerByName")
+                                        .onV("player")
+                                        .by("name")
+                                        .secondary()
+                                        .create();
+        Assert.assertTrue(playerByName.checkExist());
+        playerByName.checkExist(false);
+        Assert.assertFalse(playerByName.checkExist());
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/functional/MetricsManagerTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/functional/MetricsManagerTest.java
new file mode 100644
index 0000000..066053a
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/functional/MetricsManagerTest.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.functional;
+
+import java.util.Map;
+
+import org.junit.Test;
+
+import com.baidu.hugegraph.testutil.Assert;
+import com.google.common.collect.ImmutableSet;
+
+public class MetricsManagerTest extends BaseFuncTest {
+
+    @Test
+    public void testSystemMetrics() {
+        Map<String, Map<String, Object>> results = metrics().system();
+        Assert.assertEquals(ImmutableSet.of("basic", "heap", "nonheap",
+                                            "thread", "class_loading",
+                                            "garbage_collector"),
+                            results.keySet());
+    }
+
+    @Test
+    public void testBackendMetrics() {
+        Map<String, Map<String, Object>> results = metrics().backend();
+        Assert.assertEquals(ImmutableSet.of("hugegraph"), results.keySet());
+
+        Map<String, Object> graphResults = metrics().backend("hugegraph");
+        Assert.assertFalse(graphResults.isEmpty());
+    }
+
+    @Test
+    public void testAllMetrics() {
+        Map<String, Map<String, Object>> results = metrics().all();
+        Assert.assertEquals(ImmutableSet.of("gauges", "counters", "histograms",
+                                            "meters", "timers"),
+                            results.keySet());
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/functional/PropertyKeyTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/functional/PropertyKeyTest.java
new file mode 100644
index 0000000..df5203d
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/functional/PropertyKeyTest.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.functional;
+
+import java.util.Date;
+import java.util.List;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.baidu.hugegraph.driver.SchemaManager;
+import com.baidu.hugegraph.structure.constant.WriteType;
+import com.baidu.hugegraph.structure.schema.PropertyKey;
+import com.baidu.hugegraph.testutil.Assert;
+import com.baidu.hugegraph.testutil.Utils;
+import com.baidu.hugegraph.util.DateUtil;
+import com.google.common.collect.ImmutableList;
+
+public class PropertyKeyTest extends BaseFuncTest {
+
+    @Before
+    public void setup() {
+
+    }
+
+    @After
+    public void teardown() throws Exception {
+        BaseFuncTest.clearData();
+    }
+
+    @Test
+    public void testAddPropertyKeyWithUserData() {
+        SchemaManager schema = schema();
+
+        PropertyKey age = schema.propertyKey("age")
+                                .userdata("min", 0)
+                                .userdata("max", 100)
+                                .create();
+        Assert.assertEquals(3, age.userdata().size());
+        Assert.assertEquals(0, age.userdata().get("min"));
+        Assert.assertEquals(100, age.userdata().get("max"));
+        String time = (String) age.userdata().get("~create_time");
+        Date createTime = DateUtil.parse(time);
+        Assert.assertTrue(createTime.before(DateUtil.now()));
+
+        PropertyKey id = schema.propertyKey("id")
+                               .userdata("length", 15)
+                               .userdata("length", 18)
+                               .create();
+        // The same key user data will be overwritten
+        Assert.assertEquals(2, id.userdata().size());
+        Assert.assertEquals(18, id.userdata().get("length"));
+        time = (String) id.userdata().get("~create_time");
+        createTime = DateUtil.parse(time);
+        Assert.assertTrue(createTime.before(DateUtil.now()));
+
+        PropertyKey sex = schema.propertyKey("sex")
+                                .userdata("range",
+                                          ImmutableList.of("male", "female"))
+                                .create();
+        Assert.assertEquals(2, sex.userdata().size());
+        Assert.assertEquals(ImmutableList.of("male", "female"),
+                            sex.userdata().get("range"));
+        time = (String) sex.userdata().get("~create_time");
+        createTime = DateUtil.parse(time);
+        Assert.assertTrue(createTime.before(DateUtil.now()));
+    }
+
+    @Test
+    public void testAppendPropertyKeyWithUserData() {
+        SchemaManager schema = schema();
+        PropertyKey age = schema.propertyKey("age")
+                                .userdata("min", 0)
+                                .create();
+        Assert.assertEquals(2, age.userdata().size());
+        Assert.assertEquals(0, age.userdata().get("min"));
+        String time = (String) age.userdata().get("~create_time");
+        Date createTime = DateUtil.parse(time);
+        Assert.assertTrue(createTime.before(DateUtil.now()));
+
+        age = schema.propertyKey("age")
+                    .userdata("min", 1)
+                    .userdata("max", 100)
+                    .append();
+        Assert.assertEquals(3, age.userdata().size());
+        Assert.assertEquals(1, age.userdata().get("min"));
+        Assert.assertEquals(100, age.userdata().get("max"));
+        time = (String) age.userdata().get("~create_time");
+        Assert.assertEquals(createTime, DateUtil.parse(time));
+    }
+
+    @Test
+    public void testEliminatePropertyKeyWithUserData() {
+        SchemaManager schema = schema();
+        PropertyKey age = schema.propertyKey("age")
+                                .userdata("min", 0)
+                                .userdata("max", 100)
+                                .create();
+        Assert.assertEquals(3, age.userdata().size());
+        Assert.assertEquals(0, age.userdata().get("min"));
+        Assert.assertEquals(100, age.userdata().get("max"));
+        String time = (String) age.userdata().get("~create_time");
+        Date createTime = DateUtil.parse(time);
+        Assert.assertTrue(createTime.before(DateUtil.now()));
+
+        age = schema.propertyKey("age")
+                    .userdata("max", "")
+                    .eliminate();
+        Assert.assertEquals(2, age.userdata().size());
+        Assert.assertEquals(0, age.userdata().get("min"));
+        time = (String) age.userdata().get("~create_time");
+        Assert.assertEquals(createTime, DateUtil.parse(time));
+    }
+
+    @Test
+    public void testListByNames() {
+        SchemaManager schema = schema();
+
+        PropertyKey age = schema.propertyKey("age").create();
+        PropertyKey id = schema.propertyKey("id").create();
+
+        List<PropertyKey> propertyKeys;
+
+        propertyKeys = schema.getPropertyKeys(ImmutableList.of("age"));
+        Assert.assertEquals(1, propertyKeys.size());
+        assertContains(propertyKeys, age);
+
+        propertyKeys = schema.getPropertyKeys(ImmutableList.of("id"));
+        Assert.assertEquals(1, propertyKeys.size());
+        assertContains(propertyKeys, id);
+
+        propertyKeys = schema.getPropertyKeys(ImmutableList.of("age", "id"));
+        Assert.assertEquals(2, propertyKeys.size());
+        assertContains(propertyKeys, age);
+        assertContains(propertyKeys, id);
+    }
+
+    @Test
+    public void testResetPropertyKeyId() {
+        SchemaManager schema = schema();
+        PropertyKey age = schema.propertyKey("age")
+                                .userdata("min", 0)
+                                .userdata("max", 100)
+                                .create();
+        Assert.assertTrue(age.id() > 0);
+        age.resetId();
+        Assert.assertEquals(0L, age.id());
+    }
+
+    @Test
+    public void testSetCheckExist() {
+        SchemaManager schema = schema();
+        PropertyKey age = schema.propertyKey("age")
+                                .userdata("min", 0)
+                                .userdata("max", 100)
+                                .create();
+        Assert.assertTrue(age.checkExist());
+        age.checkExist(false);
+        Assert.assertFalse(age.checkExist());
+    }
+
+    @Test
+    public void testOlapPropertyKey() {
+        SchemaManager schema = schema();
+        PropertyKey pagerank = schema.propertyKey("pagerank")
+                                     .asDouble()
+                                     .writeType(WriteType.OLAP_RANGE)
+                                     .build();
+        schema.addPropertyKey(pagerank);
+        schema.getPropertyKey(pagerank.name());
+        schema.clearPropertyKey(pagerank);
+        schema.removePropertyKey(pagerank.name());
+        Utils.assertResponseError(404, () -> {
+            schema.getPropertyKey(pagerank.name());
+        });
+
+        long task = schema.addPropertyKeyAsync(pagerank);
+        waitUntilTaskCompleted(task);
+
+        schema.getPropertyKey(pagerank.name());
+
+        task = schema.clearPropertyKeyAsync(pagerank);
+        waitUntilTaskCompleted(task);
+
+        task = schema.removePropertyKeyAsync(pagerank.name());
+        waitUntilTaskCompleted(task);
+
+        Utils.assertResponseError(404, () -> {
+            schema.getPropertyKey(pagerank.name());
+        });
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/functional/SchemaTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/functional/SchemaTest.java
new file mode 100644
index 0000000..4689859
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/functional/SchemaTest.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.functional;
+
+import java.util.List;
+import java.util.Map;
+
+import org.junit.Test;
+
+import com.baidu.hugegraph.BaseClientTest;
+import com.baidu.hugegraph.structure.SchemaElement;
+import com.baidu.hugegraph.testutil.Assert;
+
+public class SchemaTest extends BaseFuncTest {
+
+    @Test
+    public void testlist() {
+        BaseClientTest.initPropertyKey();
+        BaseClientTest.initVertexLabel();
+        BaseClientTest.initEdgeLabel();
+
+        Map<String, List<SchemaElement>> schemas = schema().getSchema();
+
+        Assert.assertEquals(4, schemas.size());
+        Assert.assertTrue(schemas.containsKey("propertykeys"));
+        Assert.assertTrue(schemas.containsKey("vertexlabels"));
+        Assert.assertTrue(schemas.containsKey("edgelabels"));
+        Assert.assertTrue(schemas.containsKey("indexlabels"));
+        Assert.assertEquals(7, schemas.get("propertykeys").size());
+        Assert.assertEquals(3, schemas.get("vertexlabels").size());
+        Assert.assertEquals(2, schemas.get("edgelabels").size());
+        Assert.assertTrue(schemas.get("indexlabels").isEmpty());
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/functional/TraverserManagerTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/functional/TraverserManagerTest.java
new file mode 100644
index 0000000..93ca797
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/functional/TraverserManagerTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.functional;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.baidu.hugegraph.BaseClientTest;
+import com.baidu.hugegraph.structure.graph.Edge;
+import com.baidu.hugegraph.structure.graph.Shard;
+import com.baidu.hugegraph.structure.graph.Vertex;
+import com.baidu.hugegraph.testutil.Assert;
+
+public class TraverserManagerTest extends BaseFuncTest {
+
+    @Before
+    public void setup() {
+        BaseClientTest.initPropertyKey();
+        BaseClientTest.initVertexLabel();
+        BaseClientTest.initEdgeLabel();
+        BaseClientTest.initVertex();
+        BaseClientTest.initEdge();
+    }
+
+    @After
+    public void teardown() throws Exception {
+        BaseFuncTest.clearData();
+    }
+
+    @Test
+    public void testIterateVerticesByShard() {
+        List<Shard> shards = traverser().vertexShards(1 * 1024 * 1024);
+        List<Vertex> vertices = new LinkedList<>();
+        for (Shard shard : shards) {
+            Iterator<Vertex> iter = traverser().iteratorVertices(shard, 1);
+            while (iter.hasNext()) {
+                vertices.add(iter.next());
+            }
+        }
+        Assert.assertEquals(6, vertices.size());
+    }
+
+    @Test
+    public void testIterateEdgesByShard() {
+        List<Shard> shards = traverser().edgeShards(1 * 1024 * 1024);
+        List<Edge> edges = new LinkedList<>();
+        for (Shard shard : shards) {
+            Iterator<Edge> iter = traverser().iteratorEdges(shard, 1);
+            while (iter.hasNext()) {
+                edges.add(iter.next());
+            }
+        }
+        Assert.assertEquals(6, edges.size());
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/functional/VertexLabelTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/functional/VertexLabelTest.java
new file mode 100644
index 0000000..151390c
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/functional/VertexLabelTest.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.functional;
+
+import java.util.Date;
+import java.util.List;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.baidu.hugegraph.driver.SchemaManager;
+import com.baidu.hugegraph.structure.Task;
+import com.baidu.hugegraph.structure.schema.VertexLabel;
+import com.baidu.hugegraph.testutil.Assert;
+import com.baidu.hugegraph.util.DateUtil;
+import com.google.common.collect.ImmutableList;
+
+public class VertexLabelTest extends BaseFuncTest {
+
+    @Before
+    public void setup() {
+        BaseFuncTest.initPropertyKey();
+    }
+
+    @After
+    public void teardown() throws Exception {
+        BaseFuncTest.clearData();
+    }
+
+    @Test
+    public void testAddVertexLabelWithUserData() {
+        SchemaManager schema = schema();
+
+        VertexLabel player = schema.vertexLabel("player")
+                                   .properties("name")
+                                   .userdata("super_vl", "person")
+                                   .create();
+        Assert.assertEquals(2, player.userdata().size());
+        Assert.assertEquals("person", player.userdata().get("super_vl"));
+        String time = (String) player.userdata().get("~create_time");
+        Date createTime = DateUtil.parse(time);
+        Assert.assertTrue(createTime.before(DateUtil.now()));
+
+        VertexLabel runner = schema.vertexLabel("runner")
+                                   .properties("name")
+                                   .userdata("super_vl", "person")
+                                   .userdata("super_vl", "player")
+                                   .create();
+        // The same key user data will be overwritten
+        Assert.assertEquals(2, runner.userdata().size());
+        Assert.assertEquals("player", runner.userdata().get("super_vl"));
+        time = (String) runner.userdata().get("~create_time");
+        createTime = DateUtil.parse(time);
+        Assert.assertTrue(createTime.before(DateUtil.now()));
+    }
+
+    @Test
+    public void testAppendVertexLabelWithUserData() {
+        SchemaManager schema = schema();
+
+        VertexLabel player = schema.vertexLabel("player")
+                                   .properties("name")
+                                   .create();
+        Assert.assertEquals(1, player.userdata().size());
+        String time = (String) player.userdata().get("~create_time");
+        Date createTime = DateUtil.parse(time);
+        try {
+            Thread.sleep(10);
+        } catch (InterruptedException e) {
+            Assert.fail(e.getMessage());
+        }
+        Assert.assertTrue(createTime.before(DateUtil.now()));
+
+        player = schema.vertexLabel("player")
+                       .userdata("super_vl", "person")
+                       .append();
+        Assert.assertEquals(2, player.userdata().size());
+        Assert.assertEquals("person", player.userdata().get("super_vl"));
+        time = (String) player.userdata().get("~create_time");
+        Assert.assertEquals(createTime, DateUtil.parse(time));
+    }
+
+    @Test
+    public void testEliminateVertexLabelWithUserData() {
+        SchemaManager schema = schema();
+
+        VertexLabel player = schema.vertexLabel("player")
+                                   .properties("name")
+                                   .userdata("super_vl", "person")
+                                   .userdata("icon", "picture1")
+                                   .create();
+        Assert.assertEquals(3, player.userdata().size());
+        Assert.assertEquals("person", player.userdata().get("super_vl"));
+        Assert.assertEquals("picture1", player.userdata().get("icon"));
+        String time = (String) player.userdata().get("~create_time");
+        Date createTime = DateUtil.parse(time);
+        Assert.assertTrue(createTime.before(DateUtil.now()));
+
+        player = schema.vertexLabel("player")
+                       .userdata("icon", "")
+                       .eliminate();
+        Assert.assertEquals(2, player.userdata().size());
+        Assert.assertEquals("person", player.userdata().get("super_vl"));
+        time = (String) player.userdata().get("~create_time");
+        Assert.assertEquals(createTime, DateUtil.parse(time));
+    }
+
+    @Test
+    public void testRemoveVertexLabelSync() {
+        SchemaManager schema = schema();
+
+        schema.vertexLabel("player").properties("name").create();
+        // Remove vertex label sync
+        schema.removeVertexLabel("player");
+
+        schema.vertexLabel("player").properties("name").create();
+        // Remove vertex label sync with timeout
+        schema.removeVertexLabel("player", 10);
+    }
+
+    @Test
+    public void testRemoveVertexLabelASync() {
+        SchemaManager schema = schema();
+        schema.vertexLabel("player").properties("name").create();
+
+        // Remove vertex label async and wait
+        long taskId = schema.removeVertexLabelAsync("player");
+        Task task = task().waitUntilTaskCompleted(taskId, 10);
+        Assert.assertTrue(task.completed());
+    }
+
+    @Test
+    public void testListByNames() {
+        SchemaManager schema = schema();
+
+        VertexLabel player = schema.vertexLabel("player").create();
+        VertexLabel runner = schema.vertexLabel("runner").create();
+
+        List<VertexLabel> vertexLabels;
+
+        vertexLabels = schema.getVertexLabels(ImmutableList.of("player"));
+        Assert.assertEquals(1, vertexLabels.size());
+        assertContains(vertexLabels, player);
+
+        vertexLabels = schema.getVertexLabels(ImmutableList.of("runner"));
+        Assert.assertEquals(1, vertexLabels.size());
+        assertContains(vertexLabels, runner);
+
+        vertexLabels = schema.getVertexLabels(ImmutableList.of("player",
+                                                               "runner"));
+        Assert.assertEquals(2, vertexLabels.size());
+        assertContains(vertexLabels, player);
+        assertContains(vertexLabels, runner);
+    }
+
+    @Test
+    public void testResetVertexLabelId() {
+        SchemaManager schema = schema();
+        VertexLabel player = schema.vertexLabel("player")
+                                   .properties("name").create();
+        Assert.assertTrue(player.id() > 0);
+        player.resetId();
+        Assert.assertEquals(0L, player.id());
+    }
+
+    @Test
+    public void testSetCheckExist() {
+        SchemaManager schema = schema();
+        VertexLabel player = schema.vertexLabel("player")
+                                   .properties("name").build();
+        Assert.assertTrue(player.checkExist());
+        player.checkExist(false);
+        Assert.assertFalse(player.checkExist());
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/functional/VertexTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/functional/VertexTest.java
new file mode 100644
index 0000000..fab5c97
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/functional/VertexTest.java
@@ -0,0 +1,480 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.functional;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.hamcrest.CoreMatchers;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.baidu.hugegraph.BaseClientTest;
+import com.baidu.hugegraph.exception.InvalidOperationException;
+import com.baidu.hugegraph.exception.ServerException;
+import com.baidu.hugegraph.structure.constant.T;
+import com.baidu.hugegraph.structure.graph.Vertex;
+import com.baidu.hugegraph.testutil.Assert;
+import com.baidu.hugegraph.testutil.Utils;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterators;
+
+public class VertexTest extends BaseFuncTest {
+
+    @Override
+    @Before
+    public void setup() {
+        BaseClientTest.initPropertyKey();
+        BaseClientTest.initVertexLabel();
+        BaseClientTest.initEdgeLabel();
+    }
+
+    @Override
+    @After
+    public void teardown() throws Exception {
+        BaseFuncTest.clearData();
+    }
+
+    @Test
+    public void testAddVertexProperty() {
+        Vertex vadas = graph().addVertex(T.label, "person", "name", "vadas",
+                                         "age", 19);
+        Map<String, Object> props = ImmutableMap.of("name", "vadas",
+                                                    "age", 19);
+        Assert.assertEquals(props, vadas.properties());
+
+        vadas.property("city", "Beijing");
+        props = ImmutableMap.of("name", "vadas", "age", 19,
+                                "city", "Beijing");
+        Assert.assertEquals(props, vadas.properties());
+    }
+
+    @Test
+    public void testUpdateVertexProperty() {
+        Vertex vadas = graph().addVertex(T.label, "person", "name", "vadas",
+                                         "age", 19);
+        Map<String, Object> props = ImmutableMap.of("name", "vadas",
+                                                    "age", 19);
+        Assert.assertEquals(props, vadas.properties());
+
+        vadas.property("age", 20);
+        props = ImmutableMap.of("name", "vadas", "age", 20);
+        Assert.assertEquals(props, vadas.properties());
+    }
+
+    @Test
+    public void testAddVertexPropertyValueList() {
+        schema().propertyKey("time")
+                .asDate()
+                .valueList()
+                .ifNotExist()
+                .create();
+        schema().vertexLabel("person")
+                .properties("time")
+                .nullableKeys("time")
+                .append();
+
+        Vertex vadas = graph().addVertex(T.label, "person", "name", "vadas",
+                                         "age", 19, "time", "2012-10-10");
+
+        Map<String, Object> props = ImmutableMap.of(
+                                    "name", "vadas", "age", 19,
+                                    "time", ImmutableList.of(
+                                            Utils.formatDate("2012-10-10")));
+        Assert.assertEquals(props, vadas.properties());
+
+        vadas.property("time", "2014-02-14");
+        props = ImmutableMap.of("name", "vadas", "age", 19,
+                                "time", ImmutableList.of(
+                                        Utils.formatDate("2012-10-10"),
+                                        Utils.formatDate("2014-02-14")));
+        Assert.assertEquals(props, vadas.properties());
+    }
+
+    @Test
+    public void testAddVertexPropertyValueSet() {
+        schema().propertyKey("time")
+                .asDate()
+                .valueSet()
+                .ifNotExist()
+                .create();
+        schema().vertexLabel("person")
+                .properties("time")
+                .nullableKeys("time")
+                .append();
+
+        Vertex vadas = graph().addVertex(T.label, "person", "name", "vadas",
+                                         "age", 19, "time", "2012-10-10");
+
+        Map<String, Object> props = ImmutableMap.of(
+                                    "name", "vadas", "age", 19,
+                                    "time", ImmutableList.of(
+                                            Utils.formatDate("2012-10-10")));
+        Assert.assertEquals(props, vadas.properties());
+
+        vadas.property("time", "2014-02-14");
+        props = ImmutableMap.of("name", "vadas", "age", 19,
+                                "time", ImmutableList.of(
+                                        Utils.formatDate("2012-10-10"),
+                                        Utils.formatDate("2014-02-14")));
+        Assert.assertEquals(props, vadas.properties());
+    }
+
+    @Test
+    public void testAddVertexPropertyValueListWithSameValue() {
+        schema().propertyKey("time")
+                .asDate()
+                .valueList()
+                .ifNotExist()
+                .create();
+        schema().vertexLabel("person")
+                .properties("time")
+                .nullableKeys("time")
+                .append();
+
+        Vertex vadas = graph().addVertex(T.label, "person", "name", "vadas",
+                                         "age", 19, "time", "2012-10-10");
+
+        Map<String, Object> props = ImmutableMap.of(
+                                    "name", "vadas", "age", 19,
+                                    "time", ImmutableList.of(
+                                            Utils.formatDate("2012-10-10")));
+        Assert.assertEquals(props, vadas.properties());
+
+        vadas.property("time", "2012-10-10");
+        props = ImmutableMap.of("name", "vadas", "age", 19,
+                                "time", ImmutableList.of(
+                                        Utils.formatDate("2012-10-10"),
+                                        Utils.formatDate("2012-10-10")));
+        Assert.assertEquals(props, vadas.properties());
+    }
+
+    @Test
+    public void testAddVertexPropertyValueSetWithSameValue() {
+        schema().propertyKey("time")
+                .asDate()
+                .valueSet()
+                .ifNotExist()
+                .create();
+        schema().vertexLabel("person")
+                .properties("time")
+                .nullableKeys("time")
+                .append();
+
+        Vertex vadas = graph().addVertex(T.label, "person", "name", "vadas",
+                                         "age", 19, "time", "2012-10-10");
+
+        Map<String, Object> props = ImmutableMap.of(
+                                    "name", "vadas", "age", 19,
+                                    "time", ImmutableList.of(
+                                            Utils.formatDate("2012-10-10")));
+        Assert.assertEquals(props, vadas.properties());
+
+        vadas.property("time", "2012-10-10");
+        props = ImmutableMap.of("name", "vadas", "age", 19,
+                                "time", ImmutableList.of(
+                                        Utils.formatDate("2012-10-10")));
+        Assert.assertEquals(props, vadas.properties());
+    }
+
+    @Test
+    public void testAddVertexWithMapProperties() {
+        Map<String, Object> properties = ImmutableMap.of("name", "vadas",
+                                                         "age", 19,
+                                                         "city", "Beijing");
+        // 1st param is label
+        Vertex vadas = graph().addVertex("person", properties);
+        Map<String, Object> props = ImmutableMap.of("name", "vadas",
+                                                    "age", 19,
+                                                    "city", "Beijing");
+        Assert.assertEquals(props, vadas.properties());
+
+        properties = ImmutableMap.of("name", "java in action", "price", 88);
+        // 1st param is label, 2nd param is id
+        Vertex java = graph().addVertex("book", "ISBN-1", properties);
+        props = ImmutableMap.of("name", "java in action", "price", 88);
+        Assert.assertEquals(props, java.properties());
+    }
+
+    @Test
+    public void testRemoveVertexProperty() {
+        Vertex vadas = graph().addVertex(T.label, "person", "name", "vadas",
+                                         "age", 19, "city", "Beijing");
+        Map<String, Object> props = ImmutableMap.of("name", "vadas",
+                                                    "age", 19,
+                                                    "city", "Beijing");
+        Assert.assertEquals(props, vadas.properties());
+
+        vadas.removeProperty("city");
+        props = ImmutableMap.of("name", "vadas", "age", 19);
+        Assert.assertEquals(props, vadas.properties());
+    }
+
+    @Test
+    public void testRemoveVertexPropertyNotExist() {
+        Vertex vadas = graph().addVertex(T.label, "person", "name", "vadas",
+                                         "age", 19, "city", "Beijing");
+        Map<String, Object> props = ImmutableMap.of("name", "vadas",
+                                                    "age", 19,
+                                                    "city", "Beijing");
+        Assert.assertEquals(props, vadas.properties());
+
+        Assert.assertThrows(InvalidOperationException.class, () -> {
+            vadas.removeProperty("not-exist");
+        });
+    }
+
+    @Test
+    public void testGetAllVertices() {
+        BaseClientTest.initVertex();
+
+        List<Vertex> vertices = graph().listVertices();
+        Assert.assertEquals(6, vertices.size());
+        assertContains(vertices, T.label, "person", "name", "marko",
+                                 "age", 29, "city", "Beijing");
+        assertContains(vertices, T.label, "person", "name", "vadas",
+                                 "age", 27, "city", "Hongkong");
+        assertContains(vertices, T.label, "software", "name", "lop",
+                                 "lang", "java", "price", 328);
+        assertContains(vertices, T.label, "person", "name", "josh",
+                                 "age", 32, "city", "Beijing");
+        assertContains(vertices, T.label, "software", "name", "ripple",
+                                 "lang", "java", "price", 199);
+        assertContains(vertices, T.label, "person", "name", "peter",
+                                 "age", 29, "city", "Shanghai");
+    }
+
+    @Test
+    public void testGetVerticesWithNoLimit() {
+        BaseClientTest.initVertex();
+
+        List<Vertex> vertices = graph().listVertices(-1);
+        Assert.assertEquals(6, vertices.size());
+        assertContains(vertices, T.label, "person", "name", "marko",
+                                 "age", 29, "city", "Beijing");
+        assertContains(vertices, T.label, "person", "name", "vadas",
+                                 "age", 27, "city", "Hongkong");
+        assertContains(vertices, T.label, "software", "name", "lop",
+                                 "lang", "java", "price", 328);
+        assertContains(vertices, T.label, "person", "name", "josh",
+                                 "age", 32, "city", "Beijing");
+        assertContains(vertices, T.label, "software", "name", "ripple",
+                                 "lang", "java", "price", 199);
+        assertContains(vertices, T.label, "person", "name", "peter",
+                                 "age", 29, "city", "Shanghai");
+    }
+
+    @Test
+    public void testGetVerticesByLabel() {
+        BaseClientTest.initVertex();
+
+        List<Vertex> vertices = graph().listVertices("person");
+        Assert.assertEquals(4, vertices.size());
+        assertContains(vertices, T.label, "person", "name", "marko",
+                                 "age", 29, "city", "Beijing");
+        assertContains(vertices, T.label, "person", "name", "vadas",
+                                 "age", 27, "city", "Hongkong");
+        assertContains(vertices, T.label, "person", "name", "josh",
+                                 "age", 32, "city", "Beijing");
+        assertContains(vertices, T.label, "person", "name", "peter",
+                                 "age", 29, "city", "Shanghai");
+    }
+
+    @Test
+    public void testGetVerticesByLabelWithLimit2() {
+        BaseClientTest.initVertex();
+
+        List<Vertex> vertices = graph().listVertices("person", 2);
+        Assert.assertEquals(2, vertices.size());
+        for (Vertex vertex : vertices) {
+            Assert.assertEquals("person", vertex.label());
+        }
+    }
+
+    @Test
+    public void testGetVerticesByLabelAndProperties() {
+        schema().indexLabel("personByAge").range()
+                .onV("person").by("age").create();
+        BaseClientTest.initVertex();
+
+        Map<String, Object> properties = ImmutableMap.of("age", 29);
+        List<Vertex> vertices = graph().listVertices("person", properties);
+        Assert.assertEquals(2, vertices.size());
+        assertContains(vertices, T.label, "person", "name", "marko",
+                                 "age", 29, "city", "Beijing");
+        assertContains(vertices, T.label, "person", "name", "peter",
+                                 "age", 29, "city", "Shanghai");
+    }
+
+    @Test
+    public void testGetVerticesByLabelAndPropertiesWithLimit1() {
+        schema().indexLabel("personByAge").range()
+                .onV("person").by("age").create();
+        BaseClientTest.initVertex();
+
+        Map<String, Object> properties = ImmutableMap.of("age", 29);
+        List<Vertex> vertices = graph().listVertices("person", properties, 1);
+        Assert.assertEquals(1, vertices.size());
+        Assert.assertEquals("person", vertices.get(0).label());
+    }
+
+    @Test
+    public void testGetVerticesByLabelAndPropertiesWithRangeCondition() {
+        schema().indexLabel("personByAge").range()
+                .onV("person").by("age").create();
+        BaseClientTest.initVertex();
+
+        Map<String, Object> properties = ImmutableMap.of("age", "P.eq(29)");
+        List<Vertex> vertices = graph().listVertices("person", properties);
+        Assert.assertEquals(2, vertices.size());
+        for (Vertex v : vertices) {
+            Assert.assertEquals("person", v.label());
+            Assert.assertEquals(29, v.property("age"));
+        }
+
+        properties = ImmutableMap.of("age", "P.gt(29)");
+        vertices = graph().listVertices("person", properties);
+        Assert.assertEquals(1, vertices.size());
+        for (Vertex v : vertices) {
+            Assert.assertEquals("person", v.label());
+            Assert.assertGt(29, v.property("age"));
+        }
+
+        properties = ImmutableMap.of("age", "P.gte(29)");
+        vertices = graph().listVertices("person", properties);
+        Assert.assertEquals(3, vertices.size());
+        for (Vertex v : vertices) {
+            Assert.assertEquals("person", v.label());
+            Assert.assertGte(29, v.property("age"));
+        }
+
+        properties = ImmutableMap.of("age", "P.lt(29)");
+        vertices = graph().listVertices("person", properties);
+        Assert.assertEquals(1, vertices.size());
+        for (Vertex v : vertices) {
+            Assert.assertEquals("person", v.label());
+            Assert.assertLt(29, v.property("age"));
+        }
+
+        properties = ImmutableMap.of("age", "P.lte(29)");
+        vertices = graph().listVertices("person", properties);
+        Assert.assertEquals(3, vertices.size());
+        for (Vertex v : vertices) {
+            Assert.assertEquals("person", v.label());
+            Assert.assertLte(29, v.property("age"));
+        }
+
+        properties = ImmutableMap.of("age", "P.between(29,32)");
+        vertices = graph().listVertices("person", properties);
+        Assert.assertEquals(2, vertices.size());
+        for (Vertex v : vertices) {
+            Assert.assertEquals("person", v.label());
+            Assert.assertGte(29, v.property("age"));
+            Assert.assertLt(31, v.property("age"));
+        }
+
+        properties = ImmutableMap.of("age", "P.inside(27,32)");
+        vertices = graph().listVertices("person", properties);
+        Assert.assertEquals(2, vertices.size());
+        for (Vertex v : vertices) {
+            Assert.assertEquals("person", v.label());
+            Assert.assertGt(27, v.property("age"));
+            Assert.assertLt(32, v.property("age"));
+        }
+
+        properties = ImmutableMap.of("age", "P.within(27,32)");
+        vertices = graph().listVertices("person", properties);
+        Assert.assertEquals(2, vertices.size());
+        for (Vertex v : vertices) {
+            Assert.assertEquals("person", v.label());
+            Assert.assertThat(v.property("age"), CoreMatchers.anyOf(
+                              CoreMatchers.is(27), CoreMatchers.is(32)));
+        }
+    }
+
+    @Test
+    public void testGetVerticesByLabelAndPropertiesWithKeepP() {
+        schema().indexLabel("personByAge").range()
+                .onV("person").by("age").create();
+        schema().indexLabel("personByCity").secondary()
+                .onV("person").by("city").create();
+        BaseClientTest.initVertex();
+
+        Map<String, Object> properties = ImmutableMap.of("age", "P.eq(29)");
+        List<Vertex> vertices = graph().listVertices("person", properties,
+                                                     false);
+        Assert.assertEquals(2, vertices.size());
+
+        Assert.assertThrows(ServerException.class, () -> {
+            graph().listVertices("person", properties, true);
+        }, e -> {
+            Assert.assertContains("expect INT for 'age'", e.getMessage());
+        });
+
+        Map<String, Object> properties2 = ImmutableMap.of("city", "P.gt(1)");
+        vertices = graph().listVertices("person", properties2, true);
+        Assert.assertEquals(0, vertices.size());
+
+        vertices = graph().listVertices("person", properties2, true, 3);
+        Assert.assertEquals(0, vertices.size());
+    }
+
+    @Test
+    public void testIterateVerticesByLabel() {
+        BaseClientTest.initVertex();
+
+        Iterator<Vertex> vertices = graph().iterateVertices("person", 1);
+        Assert.assertEquals(4, Iterators.size(vertices));
+
+        vertices = graph().iterateVertices("software", 1);
+        Assert.assertEquals(2, Iterators.size(vertices));
+    }
+
+    @Test
+    public void testIterateVerticesByLabelAndProperties() {
+        schema().indexLabel("personByCity").secondary()
+                .onV("person").by("city").create();
+        BaseClientTest.initVertex();
+
+        Map<String, Object> properties = ImmutableMap.of("city", "Beijing");
+        Iterator<Vertex> vertices = graph().iterateVertices("person",
+                                                            properties, 1);
+        Assert.assertEquals(2, Iterators.size(vertices));
+    }
+
+    private static void assertContains(List<Vertex> vertices,
+                                       Object... keyValues) {
+        String label = Utils.getLabelValue(keyValues).get();
+        Map<String, Object> properties = Utils.asMap(keyValues);
+
+        Vertex vertex = new Vertex(label);
+        for (String key : properties.keySet()) {
+            if (key.equals(T.label)) {
+                continue;
+            }
+            vertex.property(key, properties.get(key));
+        }
+
+        Assert.assertTrue(Utils.contains(vertices, vertex));
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/testutil/Utils.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/testutil/Utils.java
new file mode 100644
index 0000000..9cdad4a
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/testutil/Utils.java
@@ -0,0 +1,321 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.testutil;
+
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import org.apache.commons.lang3.tuple.Pair;
+
+import com.baidu.hugegraph.date.SafeDateFormat;
+import com.baidu.hugegraph.exception.ServerException;
+import com.baidu.hugegraph.structure.GraphElement;
+import com.baidu.hugegraph.structure.constant.T;
+import com.baidu.hugegraph.structure.graph.Edge;
+import com.baidu.hugegraph.structure.graph.Vertex;
+import com.baidu.hugegraph.structure.schema.EdgeLabel;
+import com.baidu.hugegraph.structure.schema.IndexLabel;
+import com.baidu.hugegraph.structure.schema.PropertyKey;
+import com.baidu.hugegraph.structure.schema.VertexLabel;
+import com.baidu.hugegraph.testutil.Assert.ThrowableRunnable;
+import com.baidu.hugegraph.util.DateUtil;
+import com.google.common.collect.ImmutableList;
+
+public final class Utils {
+
+    private static final String DF = "yyyy-MM-dd HH:mm:ss.SSS";
+    private static final SafeDateFormat DATE_FORMAT = new SafeDateFormat(DF);
+
+    public static void assertResponseError(int status, ThrowableRunnable run) {
+        Assert.assertThrows(ServerException.class, run, (e) -> {
+            if (e instanceof ServerException) {
+                Assert.assertEquals("The rest status code is not matched",
+                                    status, ((ServerException) e).status());
+            }
+        });
+    }
+
+    public static void assertGraphEqual(ImmutableList<Vertex> vertices,
+                                        ImmutableList<Edge> edges,
+                                        List<Object> objects) {
+        for (Object object : objects) {
+            Assert.assertTrue(object instanceof GraphElement);
+            if (object instanceof Vertex) {
+                Assert.assertTrue(Utils.contains(vertices, (Vertex) object));
+            } else {
+                Assert.assertTrue(object instanceof Edge);
+                Assert.assertTrue(Utils.contains(edges, (Edge) object));
+            }
+        }
+    }
+
+    public static boolean contains(List<PropertyKey> propertyKeys,
+                                   PropertyKey propertyKey) {
+        for (PropertyKey pk : propertyKeys) {
+            if (equalPropertyKey(pk, propertyKey)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static boolean contains(List<VertexLabel> vertexLabels,
+                                   VertexLabel vertexLabel) {
+        for (VertexLabel vl : vertexLabels) {
+            if (equalVertexLabel(vl, vertexLabel)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static boolean contains(List<EdgeLabel> edgeLabels,
+                                   EdgeLabel edgeLabel) {
+        for (EdgeLabel el : edgeLabels) {
+            if (equalEdgeLabel(el, edgeLabel)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static boolean contains(List<IndexLabel> indexLabels,
+                                   IndexLabel indexLabel) {
+        for (IndexLabel il : indexLabels) {
+            if (equalIndexLabel(il, indexLabel)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static boolean contains(List<Vertex> vertices, Vertex vertex) {
+        for (Vertex v : vertices) {
+            if (equalVertex(v, vertex)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static boolean contains(List<Edge> edges, Edge edge) {
+        for (Edge e : edges) {
+            if (equalEdge(e, edge)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static boolean equalPropertyKey(PropertyKey left,
+                                           PropertyKey right) {
+        if (!left.name().equals(right.name())) {
+            return false;
+        }
+        if (left.dataType() != right.dataType()) {
+            return false;
+        }
+        if (left.cardinality() != right.cardinality()) {
+            return false;
+        }
+        return true;
+    }
+
+    public static boolean equalVertexLabel(VertexLabel left,
+                                           VertexLabel right) {
+        assert left != null;
+        assert right != null;
+
+        if (!left.name().equals(right.name())) {
+            return false;
+        }
+        if (left.idStrategy() != right.idStrategy()) {
+            return false;
+        }
+        if (left.properties().size() != right.properties().size() ||
+            !left.properties().containsAll(right.properties())) {
+            return false;
+        }
+        if (left.primaryKeys().size() != right.primaryKeys().size() ||
+            !left.primaryKeys().containsAll(right.primaryKeys())) {
+            return false;
+        }
+        return true;
+    }
+
+    public static boolean equalEdgeLabel(EdgeLabel left, EdgeLabel right) {
+        assert left != null;
+        assert right != null;
+
+        if (!left.name().equals(right.name())) {
+            return false;
+        }
+        if (!left.sourceLabel().equals(right.sourceLabel())) {
+            return false;
+        }
+        if (!left.targetLabel().equals(right.targetLabel())) {
+            return false;
+        }
+        if (left.frequency() != right.frequency()) {
+            return false;
+        }
+        if (left.properties().size() != right.properties().size() ||
+            !left.properties().containsAll(right.properties())) {
+            return false;
+        }
+        if (left.sortKeys().size() != right.sortKeys().size() ||
+            !left.sortKeys().containsAll(right.sortKeys())) {
+            return false;
+        }
+        return true;
+    }
+
+    private static boolean equalIndexLabel(IndexLabel left,
+                                           IndexLabel right) {
+        assert left != null;
+        assert right != null;
+
+        if (!left.name().equals(right.name())) {
+            return false;
+        }
+        if (left.baseType() != right.baseType()) {
+            return false;
+        }
+        if (!left.baseValue().equals(right.baseValue())) {
+            return false;
+        }
+        if (left.indexType() != right.indexType()) {
+            return false;
+        }
+        if (left.indexFields().size() != right.indexFields().size() ||
+            !left.indexFields().containsAll(right.indexFields())) {
+            return false;
+        }
+        return true;
+    }
+
+    private static boolean equalVertex(Vertex left, Vertex right) {
+        assert left != null;
+        assert right != null;
+
+        if (!left.label().equals(right.label())) {
+            return false;
+        }
+        Map<String, Object> leftProps = left.properties();
+        Map<String, Object> rightProps = right.properties();
+        if (leftProps.size() != rightProps.size() ||
+            !leftProps.keySet().containsAll(rightProps.keySet())) {
+            return false;
+        }
+        for (String key : leftProps.keySet()) {
+            if (!leftProps.get(key).equals(rightProps.get(key)) &&
+                leftProps.get(key) != rightProps.get(key)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private static boolean equalEdge(Edge left, Edge right) {
+        assert left != null;
+        assert right != null;
+
+        if (!left.label().equals(right.label())) {
+            return false;
+        }
+        if (!left.sourceId().equals(right.sourceId())) {
+            return false;
+        }
+        if (!left.targetId().equals(right.targetId())) {
+            return false;
+        }
+
+        // Only compare source label when the expected `right` passed
+        if (right.sourceLabel() != null) {
+            if (!left.sourceLabel().equals(right.sourceLabel())) {
+                return false;
+            }
+        }
+        // Only compare target label when the expected `right` passed
+        if (right.targetLabel() != null) {
+            if (!left.targetLabel().equals(right.targetLabel())) {
+                return false;
+            }
+        }
+
+        Map<String, Object> leftProps = left.properties();
+        Map<String, Object> rightProps = right.properties();
+        if (leftProps.size() != rightProps.size() ||
+            !leftProps.keySet().containsAll(rightProps.keySet())) {
+            return false;
+        }
+        for (String key : leftProps.keySet()) {
+            if (!leftProps.get(key).equals(rightProps.get(key)) &&
+                leftProps.get(key) != rightProps.get(key)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public static Optional<String> getLabelValue(final Object... keyValues) {
+        for (int i = 0; i < keyValues.length; i = i + 2) {
+            if (keyValues[i].equals(T.label)) {
+                return Optional.of((String) keyValues[i + 1]);
+            }
+        }
+        return Optional.empty();
+    }
+
+    public static Map<String, Object> asMap(Object... keyValues) {
+        return Utils.asPairs(keyValues).stream()
+                    .collect(Collectors.toMap(Pair::getLeft, Pair::getRight));
+    }
+
+    public static List<Pair<String, Object>> asPairs(Object... keyValues) {
+        final List<Object> list = Arrays.asList(keyValues);
+        return IntStream.range(1, list.size())
+                        .filter(i -> i % 2 != 0)
+                        .mapToObj(i -> Pair.of(list.get(i - 1).toString(),
+                                               list.get(i)))
+                        .collect(Collectors.toList());
+    }
+
+    public static long date(String date) {
+        return date(date, "yyyy-MM-dd");
+    }
+
+    public static long date(String date, String pattern) {
+        return DateUtil.parse(date, pattern).getTime();
+    }
+
+    public static String formatDate(String date) {
+        return DATE_FORMAT.format(DateUtil.parse(date));
+    }
+
+    public static String formatDate(Date date) {
+        return DATE_FORMAT.format(date);
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/unit/BaseUnitTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/unit/BaseUnitTest.java
new file mode 100644
index 0000000..a76856c
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/unit/BaseUnitTest.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.unit;
+
+import com.baidu.hugegraph.util.JsonUtil;
+
+public class BaseUnitTest {
+
+    public static <T> String serialize(T data) {
+        return JsonUtil.toJson(data);
+    }
+
+    public static <T> T deserialize(String json, Class<T> clazz) {
+        return JsonUtil.fromJson(json, clazz);
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/unit/BatchElementRequestTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/unit/BatchElementRequestTest.java
new file mode 100644
index 0000000..283cffd
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/unit/BatchElementRequestTest.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.unit;
+
+import static com.baidu.hugegraph.structure.graph.UpdateStrategy.INTERSECTION;
+
+import java.util.List;
+import java.util.Map;
+
+import org.junit.Test;
+
+import com.baidu.hugegraph.structure.graph.BatchEdgeRequest;
+import com.baidu.hugegraph.structure.graph.BatchVertexRequest;
+import com.baidu.hugegraph.structure.graph.Edge;
+import com.baidu.hugegraph.structure.graph.UpdateStrategy;
+import com.baidu.hugegraph.structure.graph.Vertex;
+import com.baidu.hugegraph.testutil.Assert;
+import com.baidu.hugegraph.testutil.Whitebox;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+public class BatchElementRequestTest extends BaseUnitTest {
+
+    @Test
+    public void testVertexRequestBuildOK() {
+        List<Vertex> vertices = ImmutableList.of(createVertex());
+        Map<String, UpdateStrategy> strategies = ImmutableMap.of("set",
+                                                                 INTERSECTION);
+
+        BatchVertexRequest req;
+        req = new BatchVertexRequest.Builder().vertices(vertices)
+                                              .updatingStrategies(strategies)
+                                              .createIfNotExist(true)
+                                              .build();
+
+        Assert.assertNotNull(req);
+        Object list = Whitebox.getInternalState(req, "vertices");
+        Assert.assertEquals(vertices, list);
+        Object map = Whitebox.getInternalState(req, "updateStrategies");
+        Assert.assertEquals(strategies, map);
+        Object created = Whitebox.getInternalState(req, "createIfNotExist");
+        Assert.assertEquals(true, created);
+    }
+
+    @Test
+    public void testVertexEmptyUpdateStrategy() {
+        List<Vertex> vertices = ImmutableList.of(createVertex());
+        Map<String, UpdateStrategy> strategies = ImmutableMap.of();
+
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            new BatchVertexRequest.Builder().vertices(vertices)
+                                            .updatingStrategies(strategies)
+                                            .createIfNotExist(true)
+                                            .build();
+        });
+    }
+
+    @Test
+    public void testVertexNotSupportedUpdateParameter() {
+        List<Vertex> vertices = ImmutableList.of(createVertex());
+        Map<String, UpdateStrategy> strategies = ImmutableMap.of("set",
+                                                                 INTERSECTION);
+
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            new BatchVertexRequest.Builder().vertices(vertices)
+                                            .updatingStrategies(strategies)
+                                            .createIfNotExist(false)
+                                            .build();
+        });
+    }
+
+    @Test
+    public void testEdgeRequestBuildOK() {
+        List<Edge> edges = ImmutableList.of(createEdge());
+        Map<String, UpdateStrategy> strategies = ImmutableMap.of("set",
+                                                                 INTERSECTION);
+
+        BatchEdgeRequest req;
+        req = new BatchEdgeRequest.Builder().edges(edges)
+                                            .updatingStrategies(strategies)
+                                            .checkVertex(false)
+                                            .createIfNotExist(true)
+                                            .build();
+
+        Assert.assertNotNull(req);
+        Object list = Whitebox.getInternalState(req, "edges");
+        Assert.assertEquals(edges, list);
+        Object map = Whitebox.getInternalState(req, "updateStrategies");
+        Assert.assertEquals(strategies, map);
+        Object checked = Whitebox.getInternalState(req, "checkVertex");
+        Assert.assertEquals(false, checked);
+        Object created = Whitebox.getInternalState(req, "createIfNotExist");
+        Assert.assertEquals(true, created);
+    }
+
+    @Test
+    public void testEdgeEmptyUpdateStrategy() {
+        List<Edge> edges = ImmutableList.of(createEdge());
+        Map<String, UpdateStrategy> strategies = ImmutableMap.of();
+
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            new BatchEdgeRequest.Builder().edges(edges)
+                                          .updatingStrategies(strategies)
+                                          .checkVertex(false)
+                                          .createIfNotExist(true)
+                                          .build();
+        });
+    }
+
+    @Test
+    public void testEdgeNotSupportedUpdateParameter() {
+        List<Edge> edges = ImmutableList.of(createEdge());
+        Map<String, UpdateStrategy> strategies = ImmutableMap.of("set",
+                                                                 INTERSECTION);
+
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            new BatchEdgeRequest.Builder().edges(edges)
+                                          .updatingStrategies(strategies)
+                                          .checkVertex(false)
+                                          .createIfNotExist(false)
+                                          .build();
+        });
+    }
+
+    private static Vertex createVertex() {
+        Vertex vertex = new Vertex("object");
+        vertex.id("object:1");
+        vertex.property("name", 1);
+        vertex.property("price", 2);
+        return vertex;
+    }
+
+    private static Edge createEdge() {
+        Edge edge = new Edge("updates");
+        edge.id("object:1>updates>>object:2");
+        edge.sourceId("object:1");
+        edge.sourceLabel("object");
+        edge.targetId("object:2");
+        edge.targetLabel("object");
+        edge.property("price", 1);
+        return edge;
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/unit/CommonUtilTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/unit/CommonUtilTest.java
new file mode 100644
index 0000000..41c55f1
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/unit/CommonUtilTest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.unit;
+
+import org.junit.Test;
+
+import com.baidu.hugegraph.testutil.Assert;
+import com.baidu.hugegraph.util.CommonUtil;
+import com.google.common.collect.ImmutableMap;
+
+public class CommonUtilTest {
+
+    @Test
+    public void testCheckMapClass() {
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            CommonUtil.checkMapClass(null, Integer.class, String.class);
+        });
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            CommonUtil.checkMapClass("Not map", Integer.class, String.class);
+        });
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            CommonUtil.checkMapClass(ImmutableMap.of(1, "1"), null,
+                                     String.class);
+        });
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            CommonUtil.checkMapClass(ImmutableMap.of(1, "1"), Integer.class,
+                                     null);
+        });
+
+        CommonUtil.checkMapClass(ImmutableMap.of(1, "1", 2, "2"),
+                                 Integer.class, String.class);
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/unit/IdUtilTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/unit/IdUtilTest.java
new file mode 100644
index 0000000..6bd0fd8
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/unit/IdUtilTest.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2019 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.unit;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import com.baidu.hugegraph.util.IdUtil;
+
+public class IdUtilTest {
+
+    @Test
+    public void testEscape() {
+        Assert.assertEquals("a2b2c",
+                            IdUtil.escape('2', '\u0000', "a", "b", "c"));
+        Assert.assertEquals("12\u0000223",
+                            IdUtil.escape('2', '\u0000', "1", "2", "3"));
+    }
+
+    @Test
+    public void testUnescape() {
+        Assert.assertArrayEquals(new String[]{"a", "b>c", "d"},
+                                 IdUtil.unescape("a>b/>c>d", ">", "/"));
+        Assert.assertEquals(1, IdUtil.unescape("", "", "").length);
+        Assert.assertEquals(1, IdUtil.unescape("foo", "bar", "baz").length);
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/unit/IndexLabelTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/unit/IndexLabelTest.java
new file mode 100644
index 0000000..8ada3dc
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/unit/IndexLabelTest.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.unit;
+
+import org.junit.Test;
+
+import com.baidu.hugegraph.exception.NotSupportException;
+import com.baidu.hugegraph.structure.constant.HugeType;
+import com.baidu.hugegraph.structure.schema.IndexLabel;
+import com.baidu.hugegraph.testutil.Assert;
+import com.baidu.hugegraph.util.JsonUtil;
+
+public class IndexLabelTest {
+
+    @Test
+    public void testIndexLabel() {
+        IndexLabel.Builder builder = new IndexLabel.BuilderImpl("personByAge",
+                                                                null);
+        IndexLabel indexLabel = builder.onV("person")
+                                       .secondary()
+                                       .by("age")
+                                       .build();
+
+        String json = "{\"name\":\"personByAge\",\"id\":0," +
+                      "\"check_exist\":true,\"user_data\":{}," +
+                      "\"base_type\":\"VERTEX_LABEL\"," +
+                      "\"base_value\":\"person\"," +
+                      "\"index_type\":\"SECONDARY\",\"fields\":[\"age\"]," +
+                      "\"rebuild\":true}";
+        Assert.assertEquals(json, JsonUtil.toJson(indexLabel));
+        Assert.assertEquals(HugeType.INDEX_LABEL.string(), indexLabel.type());
+    }
+
+    @Test
+    public void testIndexLabelV49() {
+        IndexLabel.Builder builder = new IndexLabel.BuilderImpl("personByAge",
+                                                                null);
+        IndexLabel indexLabel = builder.onV("person")
+                                       .secondary()
+                                       .by("age")
+                                       .build();
+
+        IndexLabel.IndexLabelV49 indexLabelV49 = indexLabel.switchV49();
+        // Without userdata
+        String json = "{\"id\":0,\"name\":\"personByAge\"," +
+                      "\"check_exist\":true,\"base_type\":\"VERTEX_LABEL\"," +
+                      "\"base_value\":\"person\"," +
+                      "\"index_type\":\"SECONDARY\",\"fields\":[\"age\"]}";
+        Assert.assertEquals(json, JsonUtil.toJson(indexLabelV49));
+        Assert.assertEquals(HugeType.INDEX_LABEL.string(),
+                            indexLabelV49.type());
+
+        Assert.assertThrows(NotSupportException.class, () -> {
+            indexLabelV49.userdata();
+        });
+    }
+
+    @Test
+    public void testIndexLabelV56() {
+        IndexLabel.Builder builder = new IndexLabel.BuilderImpl("personByAge",
+                                                                null);
+        IndexLabel indexLabel = builder.onV("person")
+                                       .secondary()
+                                       .by("age")
+                                       .build();
+
+        IndexLabel.IndexLabelV56 indexLabelV56 = indexLabel.switchV56();
+
+        String json = "{\"id\":0,\"name\":\"personByAge\"," +
+                "\"check_exist\":true,\"user_data\":{}," +
+                "\"base_type\":\"VERTEX_LABEL\",\"base_value\":\"person\"," +
+                "\"index_type\":\"SECONDARY\",\"fields\":[\"age\"]}";
+        Assert.assertEquals(json, JsonUtil.toJson(indexLabelV56));
+        Assert.assertEquals(HugeType.INDEX_LABEL.string(),
+                            indexLabelV56.type());
+
+        Assert.assertThrows(NotSupportException.class, () -> {
+            indexLabelV56.rebuild();
+        });
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/unit/PathSerializerTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/unit/PathSerializerTest.java
new file mode 100644
index 0000000..6d772cb
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/unit/PathSerializerTest.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.unit;
+
+import org.junit.Test;
+
+import com.baidu.hugegraph.structure.graph.Edge;
+import com.baidu.hugegraph.structure.graph.Path;
+import com.baidu.hugegraph.structure.graph.Vertex;
+import com.baidu.hugegraph.testutil.Assert;
+import com.baidu.hugegraph.testutil.Utils;
+import com.google.common.collect.ImmutableList;
+
+public class PathSerializerTest extends BaseUnitTest {
+
+    @Test
+    public void testSerializeAndDeserializePathWithVertexAndEdge() {
+        Vertex vertex = new Vertex("person");
+        vertex.id("person:marko");
+        vertex.property("name", "marko");
+        vertex.property("age", 29);
+        vertex.property("city", "Beijing");
+
+        Edge edge = new Edge("knows");
+        edge.id("person:marko>knows>>person:vadas");
+        edge.sourceId("person:marko");
+        edge.sourceLabel("person");
+        edge.targetId("person:vadas");
+        edge.targetLabel("person");
+        edge.property("date", "2016-01-10");
+        edge.property("weight", 0.5);
+
+        Path path = new Path();
+        path.labels(ImmutableList.of());
+        path.labels(ImmutableList.of());
+        path.objects(vertex);
+        path.objects(edge);
+
+        String json = serialize(path);
+        Path pathCopy = deserialize(json, Path.class);
+
+        Assert.assertEquals(2, pathCopy.objects().size());
+        Utils.assertGraphEqual(ImmutableList.of(vertex),
+                               ImmutableList.of(edge),
+                               path.objects());
+    }
+
+    @Test
+    public void testDeserializePathWithSimpleType() {
+        String json = "{"
+                + "\"labels\":["
+                + "[],"
+                + "[]"
+                + "],"
+                + "\"objects\":["
+                + "\"marko\","
+                + "\"lop\""
+                + "]"
+                + "}";
+
+        Path path = deserialize(json, Path.class);
+
+        Assert.assertEquals(2, path.labels().size());
+        Assert.assertEquals(ImmutableList.of(), path.labels().get(0));
+        Assert.assertEquals(ImmutableList.of(), path.labels().get(1));
+
+        Assert.assertEquals(2, path.objects().size());
+        Assert.assertArrayEquals(new Object[]{"marko", "lop"},
+                                 path.objects().toArray());
+
+        json = "{"
+                + "\"labels\":["
+                + "[],"
+                + "[]"
+                + "],"
+                + "\"objects\":["
+                + "29,"
+                + "32"
+                + "]"
+                + "}";
+
+        path = deserialize(json, Path.class);
+
+        Assert.assertEquals(2, path.objects().size());
+        Assert.assertArrayEquals(new Object[]{29, 32},
+                                 path.objects().toArray());
+    }
+
+    @Test
+    public void testDeserializePathWithListType() {
+        String json = "{"
+                + "\"labels\":["
+                + "[],"
+                + "[]"
+                + "],"
+                + "\"objects\":["
+                + "[\"Beijing\", \"Beijing\"],"
+                + "[\"Wuhan\", \"Hongkong\"]"
+                + "]"
+                + "}";
+
+        Path path = BaseUnitTest.deserialize(json, Path.class);
+
+        Assert.assertEquals(2, path.labels().size());
+        Assert.assertEquals(ImmutableList.of(), path.labels().get(0));
+        Assert.assertEquals(ImmutableList.of(), path.labels().get(1));
+
+        Assert.assertEquals(2, path.objects().size());
+        Assert.assertArrayEquals(new Object[]{
+                                 ImmutableList.of("Beijing", "Beijing"),
+                                 ImmutableList.of("Wuhan", "Hongkong")},
+                                 path.objects().toArray());
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/unit/PropertyKeyTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/unit/PropertyKeyTest.java
new file mode 100644
index 0000000..7b13cd3
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/unit/PropertyKeyTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.unit;
+
+import org.junit.Test;
+
+import com.baidu.hugegraph.structure.constant.Cardinality;
+import com.baidu.hugegraph.structure.constant.DataType;
+import com.baidu.hugegraph.structure.constant.HugeType;
+import com.baidu.hugegraph.structure.schema.PropertyKey;
+import com.baidu.hugegraph.testutil.Assert;
+
+public class PropertyKeyTest {
+
+    @Test
+    public void testPropertyKey() {
+        PropertyKey.Builder builder = new PropertyKey.BuilderImpl("name",
+                                                                  null);
+        PropertyKey propertyKey = builder.dataType(DataType.INT)
+                                         .cardinality(Cardinality.SINGLE)
+                                         .userdata("min", 1)
+                                         .userdata("max", 100)
+                                         .build();
+
+        String pkString = "{name=name, cardinality=SINGLE, dataType=INT, " +
+                          "aggregateType=NONE, properties=[], " +
+                          "writeType=OLTP}";
+        Assert.assertEquals(pkString, propertyKey.toString());
+        Assert.assertEquals(HugeType.PROPERTY_KEY.string(), propertyKey.type());
+        Assert.assertEquals(0, propertyKey.aggregateType().code());
+        Assert.assertEquals("none", propertyKey.aggregateType().string());
+    }
+
+    @Test
+    public void testPropertyKeyV46() {
+        PropertyKey.Builder builder = new PropertyKey.BuilderImpl("name",
+                                                                  null);
+        PropertyKey propertyKey = builder.dataType(DataType.INT)
+                                         .cardinality(Cardinality.SINGLE)
+                                         .userdata("min", 1)
+                                         .userdata("max", 100)
+                                         .build();
+
+        PropertyKey.PropertyKeyV46 propertyKeyV46 = propertyKey.switchV46();
+        String pkV46String = "{name=name, cardinality=SINGLE, " +
+                             "dataType=INT, properties=[]}";
+        Assert.assertEquals(pkV46String, propertyKeyV46.toString());
+        Assert.assertEquals(HugeType.PROPERTY_KEY.string(),
+                            propertyKeyV46.type());
+    }
+
+    @Test
+    public void testPropertyKeyV58() {
+        PropertyKey.Builder builder = new PropertyKey.BuilderImpl("name",
+                                                                  null);
+        PropertyKey propertyKey = builder.dataType(DataType.INT)
+                                         .cardinality(Cardinality.SINGLE)
+                                         .userdata("min", 1)
+                                         .userdata("max", 100)
+                                         .build();
+
+        PropertyKey.PropertyKeyV58 propertyKeyV58 = propertyKey.switchV58();
+        String pkV58String = "{name=name, cardinality=SINGLE, " +
+                             "dataType=INT, aggregateType=NONE, properties=[]}";
+        Assert.assertEquals(pkV58String, propertyKeyV58.toString());
+        Assert.assertEquals(HugeType.PROPERTY_KEY.string(),
+                            propertyKeyV58.type());
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/unit/RestResultTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/unit/RestResultTest.java
new file mode 100644
index 0000000..9b617b2
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/unit/RestResultTest.java
@@ -0,0 +1,1020 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.unit;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import com.baidu.hugegraph.driver.GraphManager;
+import com.baidu.hugegraph.rest.RestResult;
+import com.baidu.hugegraph.serializer.PathDeserializer;
+import com.baidu.hugegraph.structure.constant.Cardinality;
+import com.baidu.hugegraph.structure.constant.DataType;
+import com.baidu.hugegraph.structure.constant.Frequency;
+import com.baidu.hugegraph.structure.constant.HugeType;
+import com.baidu.hugegraph.structure.constant.IdStrategy;
+import com.baidu.hugegraph.structure.constant.IndexType;
+import com.baidu.hugegraph.structure.graph.Edge;
+import com.baidu.hugegraph.structure.graph.Path;
+import com.baidu.hugegraph.structure.graph.Vertex;
+import com.baidu.hugegraph.structure.gremlin.Response;
+import com.baidu.hugegraph.structure.gremlin.Result;
+import com.baidu.hugegraph.structure.schema.EdgeLabel;
+import com.baidu.hugegraph.structure.schema.IndexLabel;
+import com.baidu.hugegraph.structure.schema.PropertyKey;
+import com.baidu.hugegraph.structure.schema.VertexLabel;
+import com.baidu.hugegraph.testutil.Assert;
+import com.baidu.hugegraph.testutil.Utils;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+public class RestResultTest extends BaseUnitTest {
+
+    private jakarta.ws.rs.core.Response mockResponse;
+    private static GraphManager graphManager;
+
+    @BeforeClass
+    public static void init() {
+        graphManager = Mockito.mock(GraphManager.class);
+
+        SimpleModule module = new SimpleModule();
+        module.addDeserializer(Path.class, new PathDeserializer());
+        RestResult.registerModule(module);
+    }
+
+    public static GraphManager graph() {
+        return graphManager;
+    }
+
+    @Before
+    public void setup() {
+        // Mock caches
+        this.mockResponse = Mockito.mock(jakarta.ws.rs.core.Response.class);
+    }
+
+    @After
+    public void teardown() {
+        // pass
+    }
+
+    @Test
+    public void testReadPropertyKey() {
+        String json = "{"
+                + "\"id\": 3,"
+                + "\"data_type\": \"INT\","
+                + "\"name\": \"id\","
+                + "\"cardinality\": \"SINGLE\","
+                + "\"properties\": []"
+                + "}";
+
+        Mockito.when(this.mockResponse.getStatus()).thenReturn(200);
+        Mockito.when(this.mockResponse.getHeaders()).thenReturn(null);
+        Mockito.when(this.mockResponse.readEntity(String.class))
+               .thenReturn(json);
+        RestResult result = new RestResult(this.mockResponse);
+        Assert.assertEquals(200, result.status());
+        Assert.assertNull(result.headers());
+
+        PropertyKey propertyKey = result.readObject(PropertyKey.class);
+
+        Assert.assertEquals("id", propertyKey.name());
+        Assert.assertEquals(DataType.INT, propertyKey.dataType());
+        Assert.assertEquals(Cardinality.SINGLE, propertyKey.cardinality());
+        Assert.assertEquals(Collections.emptySet(), propertyKey.properties());
+    }
+
+    @Test
+    public void testReadPropertyKeys() {
+        String json = "{\"propertykeys\": ["
+                + "{"
+                + "\"id\": 3,"
+                + "\"data_type\": \"TEXT\","
+                + "\"name\": \"id\","
+                + "\"cardinality\": \"SINGLE\","
+                + "\"properties\": []"
+                + "},"
+                + "{\"id\": 4,"
+                + "\"data_type\": \"FLOAT\","
+                + "\"name\": \"date\","
+                + "\"cardinality\": \"SET\","
+                + "\"properties\": []"
+                + "}"
+                + "]}";
+
+        Mockito.when(this.mockResponse.getStatus()).thenReturn(200);
+        Mockito.when(this.mockResponse.getHeaders()).thenReturn(null);
+        Mockito.when(this.mockResponse.readEntity(String.class))
+               .thenReturn(json);
+        RestResult result = new RestResult(this.mockResponse);
+        Assert.assertEquals(200, result.status());
+        Assert.assertNull(result.headers());
+
+        List<PropertyKey> propertyKeys = result.readList("propertykeys",
+                                                         PropertyKey.class);
+        Assert.assertEquals(2, propertyKeys.size());
+        PropertyKey propertyKey1 = propertyKeys.get(0);
+        PropertyKey propertyKey2 = propertyKeys.get(1);
+
+        Assert.assertEquals("id", propertyKey1.name());
+        Assert.assertEquals(DataType.TEXT, propertyKey1.dataType());
+        Assert.assertEquals(Cardinality.SINGLE, propertyKey1.cardinality());
+        Assert.assertEquals(Collections.emptySet(), propertyKey1.properties());
+
+        Assert.assertEquals("date", propertyKey2.name());
+        Assert.assertEquals(DataType.FLOAT, propertyKey2.dataType());
+        Assert.assertEquals(Cardinality.SET, propertyKey2.cardinality());
+        Assert.assertEquals(Collections.emptySet(), propertyKey2.properties());
+    }
+
+    @Test
+    public void testReadVertexLabel() {
+        String json = "{"
+                + "\"id\": 1,"
+                + "\"primary_keys\": [\"name\"],"
+                + "\"index_labels\": [],"
+                + "\"name\": \"software\","
+                + "\"id_strategy\": \"PRIMARY_KEY\","
+                + "\"properties\": [\"price\", \"name\", \"lang\"]"
+                + "}";
+
+        Mockito.when(this.mockResponse.getStatus()).thenReturn(200);
+        Mockito.when(this.mockResponse.getHeaders()).thenReturn(null);
+        Mockito.when(this.mockResponse.readEntity(String.class))
+               .thenReturn(json);
+        RestResult result = new RestResult(this.mockResponse);
+        Assert.assertEquals(200, result.status());
+        Assert.assertNull(result.headers());
+
+        VertexLabel vertexLabel = result.readObject(VertexLabel.class);
+
+        Assert.assertEquals("software", vertexLabel.name());
+        Assert.assertEquals(IdStrategy.PRIMARY_KEY, vertexLabel.idStrategy());
+        Assert.assertEquals(ImmutableList.of("name"),
+                            vertexLabel.primaryKeys());
+        Assert.assertEquals(ImmutableSet.of("price", "name", "lang"),
+                            vertexLabel.properties());
+    }
+
+    @Test
+    public void testReadVertexLabels() {
+        String json = "{\"vertexlabels\": ["
+                + "{"
+                + "\"id\": 1,"
+                + "\"primary_keys\": [\"name\"],"
+                + "\"index_labels\": [],"
+                + "\"name\": \"software\","
+                + "\"id_strategy\": \"PRIMARY_KEY\","
+                + "\"properties\": [\"price\", \"name\", \"lang\"]"
+                + "},"
+                + "{"
+                + "\"id\": 2,"
+                + "\"primary_keys\": [],"
+                + "\"index_labels\": [],"
+                + "\"name\": \"person\","
+                + "\"id_strategy\": \"CUSTOMIZE_STRING\","
+                + "\"properties\": [\"city\", \"name\", \"age\"]"
+                + "}"
+                + "]}";
+
+        Mockito.when(this.mockResponse.getStatus()).thenReturn(200);
+        Mockito.when(this.mockResponse.getHeaders()).thenReturn(null);
+        Mockito.when(this.mockResponse.readEntity(String.class))
+               .thenReturn(json);
+        RestResult result = new RestResult(this.mockResponse);
+        Assert.assertEquals(200, result.status());
+        Assert.assertNull(result.headers());
+
+        List<VertexLabel> vertexLabels = result.readList("vertexlabels",
+                                                         VertexLabel.class);
+        Assert.assertEquals(2, vertexLabels.size());
+        VertexLabel vertexLabel1 = vertexLabels.get(0);
+        VertexLabel vertexLabel2 = vertexLabels.get(1);
+
+        Assert.assertEquals("software", vertexLabel1.name());
+        Assert.assertEquals(IdStrategy.PRIMARY_KEY, vertexLabel1.idStrategy());
+        Assert.assertEquals(ImmutableList.of("name"),
+                            vertexLabel1.primaryKeys());
+        Assert.assertEquals(ImmutableSet.of("price", "name", "lang"),
+                            vertexLabel1.properties());
+
+        Assert.assertEquals("person", vertexLabel2.name());
+        Assert.assertEquals(IdStrategy.CUSTOMIZE_STRING, vertexLabel2.idStrategy());
+        Assert.assertEquals(Collections.emptyList(),
+                            vertexLabel2.primaryKeys());
+        Assert.assertEquals(ImmutableSet.of("city", "name", "age"),
+                            vertexLabel2.properties());
+    }
+
+    @Test
+    public void testReadEdgeLabel() {
+        String json = "{"
+                + "\"id\": 2,"
+                + "\"source_label\": \"person\","
+                + "\"index_labels\": [\"createdByDate\"],"
+                + "\"name\": \"created\","
+                + "\"target_label\": \"software\","
+                + "\"sort_keys\": [],"
+                + "\"properties\": [\"date\"],"
+                + "\"frequency\": \"SINGLE\""
+                + "}";
+
+        Mockito.when(this.mockResponse.getStatus()).thenReturn(200);
+        Mockito.when(this.mockResponse.getHeaders()).thenReturn(null);
+        Mockito.when(this.mockResponse.readEntity(String.class))
+               .thenReturn(json);
+        RestResult result = new RestResult(this.mockResponse);
+        Assert.assertEquals(200, result.status());
+        Assert.assertNull(result.headers());
+
+        EdgeLabel edgeLabel = result.readObject(EdgeLabel.class);
+
+        Assert.assertEquals("created", edgeLabel.name());
+        Assert.assertEquals("person", edgeLabel.sourceLabel());
+        Assert.assertEquals("software", edgeLabel.targetLabel());
+        Assert.assertEquals(Frequency.SINGLE, edgeLabel.frequency());
+        Assert.assertEquals(Collections.emptyList(), edgeLabel.sortKeys());
+        Assert.assertEquals(ImmutableSet.of("date"), edgeLabel.properties());
+    }
+
+    @Test
+    public void testReadEdgeLabels() {
+        String json = "{\"edgelabels\": ["
+                + "{"
+                + "\"id\": 2,"
+                + "\"source_label\": \"person\","
+                + "\"index_labels\": [\"createdByDate\"],"
+                + "\"name\": \"created\","
+                + "\"target_label\": \"software\","
+                + "\"sort_keys\": [],"
+                + "\"properties\": [\"date\"],"
+                + "\"frequency\": \"SINGLE\""
+                + "},"
+                + "{\"id\": 3,"
+                + "\"source_label\": \"person\","
+                + "\"index_labels\": [],"
+                + "\"name\": \"knows\","
+                + "\"target_label\": \"person\","
+                + "\"sort_keys\": [],"
+                + "\"properties\": [\"date\", \"city\"],"
+                + "\"frequency\": \"SINGLE\""
+                + "}"
+                + "]}";
+
+        Mockito.when(this.mockResponse.getStatus()).thenReturn(200);
+        Mockito.when(this.mockResponse.getHeaders()).thenReturn(null);
+        Mockito.when(this.mockResponse.readEntity(String.class))
+               .thenReturn(json);
+        RestResult result = new RestResult(this.mockResponse);
+        Assert.assertEquals(200, result.status());
+        Assert.assertNull(result.headers());
+
+        List<EdgeLabel> edgeLabels = result.readList("edgelabels",
+                                                     EdgeLabel.class);
+        Assert.assertEquals(2, edgeLabels.size());
+        EdgeLabel edgeLabel1 = edgeLabels.get(0);
+        EdgeLabel edgeLabel2 = edgeLabels.get(1);
+
+        Assert.assertEquals("created", edgeLabel1.name());
+        Assert.assertEquals("person", edgeLabel1.sourceLabel());
+        Assert.assertEquals("software", edgeLabel1.targetLabel());
+        Assert.assertEquals(Frequency.SINGLE, edgeLabel1.frequency());
+        Assert.assertEquals(Collections.emptyList(), edgeLabel1.sortKeys());
+        Assert.assertEquals(ImmutableSet.of("date"), edgeLabel1.properties());
+
+        Assert.assertEquals("knows", edgeLabel2.name());
+        Assert.assertEquals("person", edgeLabel2.sourceLabel());
+        Assert.assertEquals("person", edgeLabel2.targetLabel());
+        Assert.assertEquals(Frequency.SINGLE, edgeLabel2.frequency());
+        Assert.assertEquals(Collections.emptyList(), edgeLabel2.sortKeys());
+        Assert.assertEquals(ImmutableSet.of("date", "city"),
+                            edgeLabel2.properties());
+    }
+
+    @Test
+    public void testReadIndexLabel() {
+        String json = "{"
+                + "\"id\": \"4\","
+                + "\"index_type\": \"SEARCH\","
+                + "\"base_value\": \"software\","
+                + "\"name\": \"softwareByPrice\","
+                + "\"fields\": [\"price\"],"
+                + "\"base_type\": \"VERTEX_LABEL\""
+                + "}";
+
+        Mockito.when(this.mockResponse.getStatus()).thenReturn(200);
+        Mockito.when(this.mockResponse.getHeaders()).thenReturn(null);
+        Mockito.when(this.mockResponse.readEntity(String.class))
+               .thenReturn(json);
+        RestResult result = new RestResult(this.mockResponse);
+        Assert.assertEquals(200, result.status());
+        Assert.assertNull(result.headers());
+
+        IndexLabel indexLabel = result.readObject(IndexLabel.class);
+
+        Assert.assertEquals("softwareByPrice", indexLabel.name());
+        Assert.assertEquals(HugeType.VERTEX_LABEL, indexLabel.baseType());
+        Assert.assertEquals("software", indexLabel.baseValue());
+        Assert.assertEquals(IndexType.SEARCH, indexLabel.indexType());
+        Assert.assertEquals(ImmutableList.of("price"),
+                            indexLabel.indexFields());
+    }
+
+    @Test
+    public void testReadIndexLabels() {
+        String json = "{\"indexlabels\": ["
+                + "{"
+                + "\"id\": \"4\","
+                + "\"index_type\": \"SEARCH\","
+                + "\"base_value\": \"software\","
+                + "\"name\": \"softwareByPrice\","
+                + "\"fields\": [\"price\"],"
+                + "\"base_type\": \"VERTEX_LABEL\""
+                + "},"
+                + "{"
+                + "\"id\": \"4\","
+                + "\"index_type\": \"SECONDARY\","
+                + "\"base_value\": \"person\","
+                + "\"name\": \"personByName\","
+                + "\"fields\": [\"name\"],"
+                + "\"base_type\": \"VERTEX_LABEL\""
+                + "}"
+                + "]}";
+
+        Mockito.when(this.mockResponse.getStatus()).thenReturn(200);
+        Mockito.when(this.mockResponse.getHeaders()).thenReturn(null);
+        Mockito.when(this.mockResponse.readEntity(String.class))
+               .thenReturn(json);
+        RestResult result = new RestResult(this.mockResponse);
+        Assert.assertEquals(200, result.status());
+        Assert.assertNull(result.headers());
+
+        List<IndexLabel> indexLabels = result.readList("indexlabels",
+                                                       IndexLabel.class);
+        Assert.assertEquals(2, indexLabels.size());
+        IndexLabel indexLabel1 = indexLabels.get(0);
+        IndexLabel indexLabel2 = indexLabels.get(1);
+
+        Assert.assertEquals("softwareByPrice", indexLabel1.name());
+        Assert.assertEquals(HugeType.VERTEX_LABEL, indexLabel1.baseType());
+        Assert.assertEquals("software", indexLabel1.baseValue());
+        Assert.assertEquals(IndexType.SEARCH, indexLabel1.indexType());
+        Assert.assertEquals(ImmutableList.of("price"),
+                            indexLabel1.indexFields());
+
+        Assert.assertEquals("personByName", indexLabel2.name());
+        Assert.assertEquals(HugeType.VERTEX_LABEL, indexLabel2.baseType());
+        Assert.assertEquals("person", indexLabel2.baseValue());
+        Assert.assertEquals(IndexType.SECONDARY, indexLabel2.indexType());
+        Assert.assertEquals(ImmutableList.of("name"),
+                            indexLabel2.indexFields());
+    }
+
+    @Test
+    public void testReadVertex() {
+        String json = "{"
+                + "\"id\": \"person:marko\","
+                + "\"label\": \"person\","
+                + "\"type\": \"vertex\","
+                + "\"properties\": {"
+                + "\"name\": \"marko\""
+                + "}"
+                + "}";
+
+        Mockito.when(this.mockResponse.getStatus()).thenReturn(200);
+        Mockito.when(this.mockResponse.getHeaders()).thenReturn(null);
+        Mockito.when(this.mockResponse.readEntity(String.class))
+               .thenReturn(json);
+        RestResult result = new RestResult(this.mockResponse);
+        Assert.assertEquals(200, result.status());
+        Assert.assertNull(result.headers());
+
+        Vertex vertex = result.readObject(Vertex.class);
+
+        Assert.assertEquals("person:marko", vertex.id());
+        Assert.assertEquals("person", vertex.label());
+        Assert.assertEquals(ImmutableMap.of("name", "marko"),
+                            vertex.properties());
+    }
+
+    @Test
+    public void testReadVertices() {
+        String json = "{\"vertices\": ["
+                + "{"
+                + "\"id\": \"person:marko\","
+                + "\"label\": \"person\","
+                + "\"type\": \"vertex\","
+                + "\"properties\": {"
+                + "\"city\": [\"Beijing\",\"Wuhan\",\"Beijing\"],"
+                + "\"name\": \"marko\","
+                + "\"age\": 29"
+                + "}"
+                + "},"
+                + "{"
+                + "\"id\": \"software:lop\","
+                + "\"label\": \"software\","
+                + "\"type\": \"vertex\","
+                + "\"properties\": {"
+                + "\"price\": 328,"
+                + "\"name\": \"lop\","
+                + "\"lang\": [\"java\",\"python\",\"c++\"]"
+                + "}"
+                + "},"
+                + "{"
+                + "\"id\": \"person:peter\","
+                + "\"label\": \"person\","
+                + "\"type\": \"vertex\","
+                + "\"properties\": {"
+                + "\"city\": [\"Shanghai\"],"
+                + "\"name\": \"peter\","
+                + "\"age\": 29"
+                + "}"
+                + "}"
+                + "]}";
+
+        Mockito.when(this.mockResponse.getStatus()).thenReturn(200);
+        Mockito.when(this.mockResponse.getHeaders()).thenReturn(null);
+        Mockito.when(this.mockResponse.readEntity(String.class))
+               .thenReturn(json);
+        RestResult result = new RestResult(this.mockResponse);
+        Assert.assertEquals(200, result.status());
+        Assert.assertNull(result.headers());
+
+        List<Vertex> vertices = result.readList("vertices", Vertex.class);
+        Assert.assertEquals(3, vertices.size());
+        Vertex vertex1 = vertices.get(0);
+        Vertex vertex2 = vertices.get(1);
+        Vertex vertex3 = vertices.get(2);
+
+        Assert.assertEquals("person:marko", vertex1.id());
+        Assert.assertEquals("person", vertex1.label());
+        Assert.assertEquals(ImmutableMap.of(
+                            "name", "marko",
+                            "age", 29,
+                            "city", ImmutableList.of("Beijing", "Wuhan",
+                                                     "Beijing")
+                            ),
+                            vertex1.properties());
+
+        Assert.assertEquals("software:lop", vertex2.id());
+        Assert.assertEquals("software", vertex2.label());
+        Assert.assertEquals(ImmutableMap.of(
+                            "name", "lop",
+                            "lang", ImmutableList.of("java", "python", "c++"),
+                            "price", 328),
+                            vertex2.properties());
+
+        Assert.assertEquals("person:peter", vertex3.id());
+        Assert.assertEquals("person", vertex3.label());
+        Assert.assertEquals(ImmutableMap.of(
+                            "name", "peter",
+                            "age", 29,
+                            "city", ImmutableList.of("Shanghai")),
+                            vertex3.properties());
+    }
+
+    @Test
+    public void testReadEdge() {
+        String json = "{"
+                + "\"id\": \"person:peter>created>>software:lop\","
+                + "\"label\": \"created\","
+                + "\"type\": \"edge\","
+                + "\"outV\": \"person:peter\","
+                + "\"inV\": \"software:lop\","
+                + "\"outVLabel\": \"person\","
+                + "\"inVLabel\": \"software\","
+                + "\"properties\": {"
+                + "\"city\": \"Hongkong\","
+                + "\"date\": 1495036800000"
+                + "}"
+                + "}";
+
+        Mockito.when(this.mockResponse.getStatus()).thenReturn(200);
+        Mockito.when(this.mockResponse.getHeaders()).thenReturn(null);
+        Mockito.when(this.mockResponse.readEntity(String.class))
+               .thenReturn(json);
+        RestResult result = new RestResult(this.mockResponse);
+        Assert.assertEquals(200, result.status());
+        Assert.assertNull(result.headers());
+
+        Edge edge = result.readObject(Edge.class);
+
+        Assert.assertEquals("person:peter>created>>software:lop", edge.id());
+        Assert.assertEquals("created", edge.label());
+        Assert.assertEquals("person:peter", edge.sourceId());
+        Assert.assertEquals("software:lop", edge.targetId());
+        Assert.assertEquals("person", edge.sourceLabel());
+        Assert.assertEquals("software", edge.targetLabel());
+        Assert.assertEquals(ImmutableMap.of("city", "Hongkong",
+                                            "date", 1495036800000L),
+                            edge.properties());
+    }
+
+    @Test
+    public void testReadEdges() {
+        String json = "{\"edges\": ["
+                + "{"
+                + "\"id\": \"person:peter>created>>software:lop\","
+                + "\"label\": \"created\","
+                + "\"type\": \"edge\","
+                + "\"inVLabel\": \"software\","
+                + "\"outVLabel\": \"person\","
+                + "\"inV\": \"software:lop\","
+                + "\"outV\": \"person:peter\","
+                + "\"properties\": {"
+                + "\"date\": 1495036800000,"
+                + "\"city\": \"Hongkong\""
+                + "}"
+                + "},"
+                + "{"
+                + "\"id\": \"person:peter>knows>>person:marko\","
+                + "\"label\": \"knows\","
+                + "\"type\": \"edge\","
+                + "\"inVLabel\": \"person\","
+                + "\"outVLabel\": \"person\","
+                + "\"inV\": \"person:marko\","
+                + "\"outV\": \"person:peter\","
+                + "\"properties\": {"
+                + "\"date\": 1476720000000"
+                + "}"
+                + "}"
+                + "]}";
+
+        Mockito.when(this.mockResponse.getStatus()).thenReturn(200);
+        Mockito.when(this.mockResponse.getHeaders()).thenReturn(null);
+        Mockito.when(this.mockResponse.readEntity(String.class))
+               .thenReturn(json);
+        RestResult result = new RestResult(this.mockResponse);
+        Assert.assertEquals(200, result.status());
+        Assert.assertNull(result.headers());
+
+        List<Edge> edges = result.readList("edges", Edge.class);
+        Assert.assertEquals(2, edges.size());
+        Edge edge1 = edges.get(0);
+        Edge edge2 = edges.get(1);
+
+        Assert.assertEquals("person:peter>created>>software:lop", edge1.id());
+        Assert.assertEquals("created", edge1.label());
+        Assert.assertEquals("person:peter", edge1.sourceId());
+        Assert.assertEquals("software:lop", edge1.targetId());
+        Assert.assertEquals("person", edge1.sourceLabel());
+        Assert.assertEquals("software", edge1.targetLabel());
+        Assert.assertEquals(ImmutableMap.of("city", "Hongkong",
+                                            "date", 1495036800000L),
+                            edge1.properties());
+
+        Assert.assertEquals("person:peter>knows>>person:marko", edge2.id());
+        Assert.assertEquals("knows", edge2.label());
+        Assert.assertEquals("person:peter", edge2.sourceId());
+        Assert.assertEquals("person:marko", edge2.targetId());
+        Assert.assertEquals("person", edge2.sourceLabel());
+        Assert.assertEquals("person", edge2.targetLabel());
+        Assert.assertEquals(ImmutableMap.of("date", 1476720000000L),
+                            edge2.properties());
+    }
+
+    @Test
+    public void testReadGremlinVertices() {
+        String json = "{"
+                + "\"requestId\": \"b0fd8ead-333f-43ac-97b0-4d78784726ae\","
+                + "\"status\": {"
+                + "\"message\": \"\","
+                + "\"code\": 200,"
+                + "\"attributes\": {}"
+                + "},"
+                + "\"result\": {"
+                + "\"data\": ["
+                + "{"
+                + "\"id\": \"person:marko\","
+                + "\"label\": \"person\","
+                + "\"type\": \"vertex\","
+                + "\"properties\": {"
+                + "\"city\": [\"Beijing\",\"Wuhan\",\"Beijing\"],"
+                + "\"name\": \"marko\","
+                + "\"age\": 29"
+                + "}"
+                + "},"
+                + "{"
+                + "\"id\": \"software:lop\","
+                + "\"label\": \"software\","
+                + "\"type\": \"vertex\","
+                + "\"properties\": {"
+                + "\"price\": 328,"
+                + "\"name\": \"lop\","
+                + "\"lang\": [\"java\",\"python\",\"c++\"]"
+                + "}"
+                + "},"
+                + "{"
+                + "\"id\": \"person:peter\","
+                + "\"label\": \"person\","
+                + "\"type\": \"vertex\","
+                + "\"properties\": {"
+                + "\"city\": [\"Shanghai\"],"
+                + "\"name\": \"peter\","
+                + "\"age\": 35"
+                + "}"
+                + "}"
+                + "],"
+                + "\"meta\": {}"
+                + "}"
+                + "}";
+
+        Mockito.when(this.mockResponse.getStatus()).thenReturn(200);
+        Mockito.when(this.mockResponse.getHeaders()).thenReturn(null);
+        Mockito.when(this.mockResponse.readEntity(String.class))
+               .thenReturn(json);
+        RestResult restResult = new RestResult(this.mockResponse);
+        Assert.assertEquals(200, restResult.status());
+        Assert.assertNull(restResult.headers());
+
+        Response response = restResult.readObject(Response.class);
+        response.graphManager(graph());
+        Assert.assertEquals("b0fd8ead-333f-43ac-97b0-4d78784726ae",
+                            response.requestId());
+        Assert.assertEquals(200, response.status().code());
+
+        Vertex marko = new Vertex("person");
+        marko.id("person:marko");
+        marko.property("name", "marko");
+        marko.property("city", ImmutableList.of("Beijing", "Wuhan", "Beijing"));
+        marko.property("age", 29);
+
+        Vertex lop = new Vertex("software");
+        lop.id("software:lop");
+        lop.property("name", "lop");
+        lop.property("lang", ImmutableList.of("java", "python", "c++"));
+        lop.property("price", 328);
+
+        Vertex peter = new Vertex("person");
+        peter.id("person:peter");
+        peter.property("name", "peter");
+        peter.property("city", ImmutableList.of("Shanghai"));
+        peter.property("age", 35);
+
+        List<Vertex> vertices = new ArrayList<>(3);
+        vertices.add(peter);
+        vertices.add(marko);
+        vertices.add(lop);
+
+        Iterator<Result> results = response.result().iterator();
+        while (results.hasNext()) {
+            Result result = results.next();
+            Assert.assertEquals(Vertex.class, result.getObject().getClass());
+            Vertex vertex = result.getVertex();
+            Assert.assertTrue(Utils.contains(vertices, vertex));
+        }
+    }
+
+    @Test
+    public void testReadGremlinEdges() {
+        String json = "{"
+                + "\"requestId\": \"cd4cfc17-1ee4-4e9e-af40-cb18b115a8dc\","
+                + "\"status\": {"
+                + "\"message\": \"\","
+                + "\"code\": 200,"
+                + "\"attributes\": {}"
+                + "},"
+                + "\"result\": {"
+                + "\"data\": ["
+                + "{"
+                + "\"id\": \"person:peter>created>>software:lop\","
+                + "\"label\": \"created\","
+                + "\"type\": \"edge\","
+                + "\"inVLabel\": \"software\","
+                + "\"outVLabel\": \"person\","
+                + "\"inV\": \"software:lop\","
+                + "\"outV\": \"person:peter\","
+                + "\"properties\": {"
+                + "\"date\": 1490284800000,"
+                + "\"weight\": 0.2"
+                + "}"
+                + "},"
+                + "{"
+                + "\"id\": \"person:peter>knows>>person:marko\","
+                + "\"label\": \"knows\","
+                + "\"type\": \"edge\","
+                + "\"inVLabel\": \"person\","
+                + "\"outVLabel\": \"person\","
+                + "\"inV\": \"person:marko\","
+                + "\"outV\": \"person:peter\","
+                + "\"properties\": {"
+                + "\"date\": 1452355200000,"
+                + "\"weight\": 0.5"
+                + "}"
+                + "}"
+                + "],"
+                + "\"meta\": {}"
+                + "}"
+                + "}";
+
+        Mockito.when(this.mockResponse.getStatus()).thenReturn(200);
+        Mockito.when(this.mockResponse.getHeaders()).thenReturn(null);
+        Mockito.when(this.mockResponse.readEntity(String.class))
+               .thenReturn(json);
+        RestResult restResult = new RestResult(this.mockResponse);
+        Assert.assertEquals(200, restResult.status());
+        Assert.assertNull(restResult.headers());
+
+        Response response = restResult.readObject(Response.class);
+        response.graphManager(graph());
+        Assert.assertEquals(200, response.status().code());
+
+        Edge created = new Edge("created");
+        created.id("person:peter>created>>software:lop");
+        created.sourceId("person:peter");
+        created.targetId("software:lop");
+        created.sourceLabel("person");
+        created.targetLabel("software");
+        created.property("date", 1490284800000L);
+        created.property("weight", 0.2);
+
+        Edge knows = new Edge("knows");
+        knows.id("person:peter>knows>>person:marko");
+        knows.sourceId("person:peter");
+        knows.targetId("person:marko");
+        knows.sourceLabel("person");
+        knows.targetLabel("person");
+        knows.property("date", 1452355200000L);
+        knows.property("weight", 0.5);
+
+        List<Edge> edges = new ArrayList<>(2);
+        edges.add(created);
+        edges.add(knows);
+
+        Iterator<Result> results = response.result().iterator();
+        while (results.hasNext()) {
+            Result result = results.next();
+            Assert.assertEquals(Edge.class, result.getObject().getClass());
+            Edge edge = result.getEdge();
+            Assert.assertTrue(Utils.contains(edges, edge));
+        }
+    }
+
+    @Test
+    public void testReadGremlinPathWithVertexAndEdge() {
+        String json = "{"
+                + "\"requestId\": \"238c74ca-18f7-4377-b8e1-2bb3b165e5d6\","
+                + "\"status\":{"
+                + "\"message\": \"\","
+                + "\"code\": 200,"
+                + "\"attributes\":{}"
+                + "},"
+                + "\"result\":{"
+                + "\"data\":["
+                + "{"
+                + "\"labels\":[[], []],"
+                + "\"objects\":["
+                + "{"
+                + "\"id\": \"person:marko\","
+                + "\"label\": \"person\","
+                + "\"type\": \"vertex\","
+                + "\"properties\":{"
+                + "\"city\":\"Beijing\","
+                + "\"name\":\"marko\","
+                + "\"age\":29"
+                + "}"
+                + "},"
+                + "{"
+                + "\"id\": \"person:marko>knows>>person:vadas\","
+                + "\"label\": \"knows\","
+                + "\"type\": \"edge\","
+                + "\"inVLabel\": \"person\","
+                + "\"outVLabel\": \"person\","
+                + "\"inV\": \"person:vadas\","
+                + "\"outV\": \"person:marko\","
+                + "\"properties\":{"
+                + "\"date\": 1452355200000,"
+                + "\"weight\": 0.5"
+                + "}"
+                + "}"
+                + "]"
+                + "}"
+                + "],"
+                + "\"meta\":{"
+                + "}"
+                + "}"
+                + "}";
+
+        Mockito.when(this.mockResponse.getStatus()).thenReturn(200);
+        Mockito.when(this.mockResponse.getHeaders()).thenReturn(null);
+        Mockito.when(this.mockResponse.readEntity(String.class))
+               .thenReturn(json);
+        RestResult restResult = new RestResult(this.mockResponse);
+        Assert.assertEquals(200, restResult.status());
+        Assert.assertNull(restResult.headers());
+
+        Response response = restResult.readObject(Response.class);
+        response.graphManager(graph());
+        Assert.assertEquals(200, response.status().code());
+
+        Iterator<Result> results = response.result().iterator();
+        Assert.assertTrue(results.hasNext());
+        Result result = results.next();
+        Object object = result.getObject();
+        Assert.assertEquals(Path.class, object.getClass());
+        Path path = (Path) object;
+        Assert.assertEquals(2, path.labels().size());
+        Assert.assertEquals(ImmutableList.of(), path.labels().get(0));
+        Assert.assertEquals(ImmutableList.of(), path.labels().get(1));
+
+        Vertex vertex = new Vertex("person");
+        vertex.id("person:marko");
+        vertex.property("name", "marko");
+        vertex.property("age", 29);
+        vertex.property("city", "Beijing");
+
+        Edge edge = new Edge("knows");
+        edge.id("person:marko>knows>>person:vadas");
+        edge.sourceId("person:marko");
+        edge.sourceLabel("person");
+        edge.targetId("person:vadas");
+        edge.targetLabel("person");
+        edge.property("date", 1452355200000L);
+        edge.property("weight", 0.5);
+
+        Assert.assertEquals(2, path.objects().size());
+        Utils.assertGraphEqual(ImmutableList.of(vertex),
+                               ImmutableList.of(edge),
+                               path.objects());
+    }
+
+    @Test
+    public void testReadGremlinNullData() {
+        String json = "{"
+                + "\"requestId\": \"d95ac131-24b5-4140-a3ff-91b0c020764a\","
+                + "\"status\": {"
+                + "\"message\": \"\","
+                + "\"code\": 200,"
+                + "\"attributes\": {}"
+                + "},"
+                + "\"result\": {"
+                + "\"data\": [null],"
+                + "\"meta\": {}"
+                + "}"
+                + "}";
+
+        Mockito.when(this.mockResponse.getStatus()).thenReturn(200);
+        Mockito.when(this.mockResponse.getHeaders()).thenReturn(null);
+        Mockito.when(this.mockResponse.readEntity(String.class))
+               .thenReturn(json);
+        RestResult restResult = new RestResult(this.mockResponse);
+        Assert.assertEquals(200, restResult.status());
+        Assert.assertNull(restResult.headers());
+
+        Response response = restResult.readObject(Response.class);
+        response.graphManager(graph());
+        Assert.assertEquals(200, response.status().code());
+
+        Iterator<Result> results = response.result().iterator();
+        Assert.assertTrue(results.hasNext());
+        Object object = results.next();
+        Assert.assertNull(object);
+    }
+
+    @Test
+    public void testReadGremlinNullAndVertex() {
+        String json = "{"
+                + "\"requestId\": \"d95ac131-24b5-4140-a3ff-91b0c020764a\","
+                + "\"status\": {"
+                + "\"message\": \"\","
+                + "\"code\": 200,"
+                + "\"attributes\": {}"
+                + "},"
+                + "\"result\": {"
+                + "\"data\": ["
+                + "null,"
+                + "{"
+                + "\"id\": \"person:marko\","
+                + "\"label\": \"person\","
+                + "\"type\": \"vertex\","
+                + "\"properties\": {"
+                + "\"city\": \"Beijing\","
+                + "\"name\": \"marko\","
+                + "\"age\": 29"
+                + "}"
+                + "}"
+                + "],"
+                + "\"meta\": {}"
+                + "}"
+                + "}";
+
+        Mockito.when(this.mockResponse.getStatus()).thenReturn(200);
+        Mockito.when(this.mockResponse.getHeaders()).thenReturn(null);
+        Mockito.when(this.mockResponse.readEntity(String.class))
+                .thenReturn(json);
+        RestResult restResult = new RestResult(this.mockResponse);
+        Assert.assertEquals(200, restResult.status());
+        Assert.assertNull(restResult.headers());
+
+        Response response = restResult.readObject(Response.class);
+        response.graphManager(graph());
+        Assert.assertEquals(200, response.status().code());
+
+        Iterator<Result> results = response.result().iterator();
+        Assert.assertTrue(results.hasNext());
+        Result result = results.next();
+        Assert.assertNull(result);
+
+        Assert.assertTrue(results.hasNext());
+        result = results.next();
+        Assert.assertEquals(Vertex.class, result.getObject().getClass());
+
+        Vertex marko = new Vertex("person");
+        marko.id("person:marko");
+        marko.property("name", "marko");
+        marko.property("city", "Beijing");
+        marko.property("age", 29);
+        Vertex vertex = result.getVertex();
+        Assert.assertTrue(Utils.contains(ImmutableList.of(marko), vertex));
+    }
+
+    @Test
+    public void testReadGremlinEdgeAndNull() {
+        String json = "{"
+                + "\"requestId\": \"d95ac131-24b5-4140-a3ff-91b0c020764a\","
+                + "\"status\": {"
+                + "\"message\": \"\","
+                + "\"code\": 200,"
+                + "\"attributes\": {}"
+                + "},"
+                + "\"result\": {"
+                + "\"data\": ["
+                + "{"
+                + "\"id\": \"person:peter>created>>software:lop\","
+                + "\"label\": \"created\","
+                + "\"type\": \"edge\","
+                + "\"inVLabel\": \"software\","
+                + "\"outVLabel\": \"person\","
+                + "\"inV\": \"software:lop\","
+                + "\"outV\": \"person:peter\","
+                + "\"properties\": {"
+                + "\"date\": 1490284800000,"
+                + "\"weight\": 0.2"
+                + "}"
+                + "},"
+                + "null"
+                + "],"
+                + "\"meta\": {}"
+                + "}"
+                + "}";
+
+        Mockito.when(this.mockResponse.getStatus()).thenReturn(200);
+        Mockito.when(this.mockResponse.getHeaders()).thenReturn(null);
+        Mockito.when(this.mockResponse.readEntity(String.class))
+               .thenReturn(json);
+        RestResult restResult = new RestResult(this.mockResponse);
+        Assert.assertEquals(200, restResult.status());
+        Assert.assertNull(restResult.headers());
+
+        Response response = restResult.readObject(Response.class);
+        response.graphManager(graph());
+        Assert.assertEquals(200, response.status().code());
+
+        Iterator<Result> results = response.result().iterator();
+
+        Assert.assertTrue(results.hasNext());
+        Result result = results.next();
+        Assert.assertEquals(Edge.class, result.getObject().getClass());
+
+        Edge created = new Edge("created");
+        created.id("person:peter>created>>software:lop");
+        created.sourceId("person:peter");
+        created.targetId("software:lop");
+        created.sourceLabel("person");
+        created.targetLabel("software");
+        created.property("date", 1490284800000L);
+        created.property("weight", 0.2);
+        Assert.assertTrue(Utils.contains(ImmutableList.of(created),
+                                         result.getEdge()));
+
+        Assert.assertTrue(results.hasNext());
+        result = results.next();
+        Assert.assertNull(result);
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/unit/SplicingIdGeneratorTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/unit/SplicingIdGeneratorTest.java
new file mode 100644
index 0000000..bd9e231
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/unit/SplicingIdGeneratorTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.unit;
+
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import com.baidu.hugegraph.util.SplicingIdGenerator;
+import com.google.common.collect.ImmutableList;
+
+public class SplicingIdGeneratorTest {
+
+    @Test
+    public void testConcatIds() {
+        Assert.assertEquals("a>b>c>d",
+                            SplicingIdGenerator.concat("a", "b", "c", "d"));
+        // > => `>
+        Assert.assertEquals("a>`>>c>`>",
+                            SplicingIdGenerator.concat("a", ">", "c", ">"));
+        Assert.assertEquals("a>b`>c>d",
+                            SplicingIdGenerator.concat("a", "b>c", "d"));
+    }
+
+    @Test
+    public void testSplitIds() {
+        Assert.assertArrayEquals(new String[]{"a", "b", "c", "d"},
+                                 SplicingIdGenerator.split("a>b>c>d"));
+        Assert.assertArrayEquals(new String[]{"a", ">", "c", ">"},
+                                 SplicingIdGenerator.split("a>`>>c>`>"));
+        Assert.assertArrayEquals(new String[]{"a", "b>c", "d"},
+                                 SplicingIdGenerator.split("a>b`>c>d"));
+    }
+
+    @Test
+    public void testConcatValues() {
+        Assert.assertEquals("a!1!c!d",
+                            SplicingIdGenerator.concatValues("a", 1, 'c', "d"));
+        Assert.assertEquals("a!`!!1!d",
+                            SplicingIdGenerator.concatValues("a", "!", 1, 'd'));
+        Assert.assertEquals("a!b`!c!d",
+                            SplicingIdGenerator.concatValues("a", "b!c", "d"));
+
+        List<Object> values = ImmutableList.of("a", 1, 'c', "d");
+        Assert.assertEquals("a!1!c!d",
+                            SplicingIdGenerator.concatValues(values));
+        values = ImmutableList.of("a", "!", 1, 'd');
+        Assert.assertEquals("a!`!!1!d",
+                            SplicingIdGenerator.concatValues(values));
+        values = ImmutableList.of("a", "b!c", "d");
+        Assert.assertEquals("a!b`!c!d",
+                            SplicingIdGenerator.concatValues(values));
+    }
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/unit/UnitTestSuite.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/unit/UnitTestSuite.java
new file mode 100644
index 0000000..dcc5417
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/unit/UnitTestSuite.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.unit;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+@RunWith(Suite.class)
+@Suite.SuiteClasses({
+    VertexSerializerTest.class,
+    PathSerializerTest.class,
+    RestResultTest.class,
+    BatchElementRequestTest.class,
+    PropertyKeyTest.class,
+    IndexLabelTest.class,
+    CommonUtilTest.class,
+    IdUtilTest.class,
+    SplicingIdGeneratorTest.class
+})
+public class UnitTestSuite {
+}
diff --git a/hugegraph-client/src/test/java/com/baidu/hugegraph/unit/VertexSerializerTest.java b/hugegraph-client/src/test/java/com/baidu/hugegraph/unit/VertexSerializerTest.java
new file mode 100644
index 0000000..a232090
--- /dev/null
+++ b/hugegraph-client/src/test/java/com/baidu/hugegraph/unit/VertexSerializerTest.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.unit;
+
+import java.util.Map;
+
+import org.junit.Test;
+
+import com.baidu.hugegraph.structure.graph.Vertex;
+import com.baidu.hugegraph.testutil.Assert;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+public class VertexSerializerTest extends BaseUnitTest {
+
+    @Test
+    public void testSerializeAndDeserializeVertex() {
+        Vertex vertex = new Vertex("person");
+        vertex.id("person:marko");
+        vertex.property("name", "marko");
+        vertex.property("age", 29);
+        vertex.property("city", "Beijing");
+
+        String json = serialize(vertex);
+        Vertex vertexCopy = deserialize(json, Vertex.class);
+
+        Assert.assertEquals("person:marko", vertexCopy.id());
+        Assert.assertEquals("person", vertexCopy.label());
+        Assert.assertEquals("vertex", vertexCopy.type());
+        Map<String, Object> props = ImmutableMap.of("name", "marko",
+                                                    "age", 29,
+                                                    "city", "Beijing");
+        Assert.assertEquals(props, vertexCopy.properties());
+    }
+
+    @Test
+    public void testSerializeAndDeserializeVertexWithListProp() {
+        Vertex vertex = new Vertex("person");
+        vertex.id("person:marko");
+        vertex.property("name", "marko");
+        vertex.property("age", 29);
+        vertex.property("city", ImmutableList.of("Hefei", "Wuhan"));
+
+        String json = serialize(vertex);
+        Vertex vertexCopy = deserialize(json, Vertex.class);
+
+        Assert.assertEquals("person:marko", vertexCopy.id());
+        Assert.assertEquals("person", vertexCopy.label());
+        Assert.assertEquals("vertex", vertexCopy.type());
+        Map<String, Object> props = ImmutableMap.of(
+                                    "name", "marko", "age", 29,
+                                    "city", ImmutableList.of("Hefei", "Wuhan"));
+        Assert.assertEquals(props, vertexCopy.properties());
+    }
+
+    @Test
+    public void testSerializeAndDeserializeVertexWithSetProp() {
+        Vertex vertex = new Vertex("person");
+        vertex.id("person:marko");
+        vertex.property("name", "marko");
+        vertex.property("age", 29);
+        vertex.property("city", ImmutableSet.of("Hefei", "Wuhan", "Wuhan"));
+
+        String json = serialize(vertex);
+        Vertex vertexCopy = deserialize(json, Vertex.class);
+
+        Assert.assertEquals("person:marko", vertexCopy.id());
+        Assert.assertEquals("person", vertexCopy.label());
+        Assert.assertEquals("vertex", vertexCopy.type());
+        // TODO: Set properties should deserialize to Set instead of List
+        Map<String, Object> props = ImmutableMap.of(
+                                    "name", "marko", "age", 29,
+                                    "city", ImmutableList.of("Hefei", "Wuhan"));
+        Assert.assertEquals(props, vertexCopy.properties());
+    }
+}
diff --git a/hugegraph-client/src/test/resources/hugegraph-clone.properties b/hugegraph-client/src/test/resources/hugegraph-clone.properties
new file mode 100644
index 0000000..712b663
--- /dev/null
+++ b/hugegraph-client/src/test/resources/hugegraph-clone.properties
@@ -0,0 +1,3 @@
+store=hugegraph3
+rocksdb.data_path=./hg3
+rocksdb.wal_path=./hg3
diff --git a/hugegraph-client/src/test/resources/hugegraph-create.properties b/hugegraph-client/src/test/resources/hugegraph-create.properties
new file mode 100644
index 0000000..cc022b0
--- /dev/null
+++ b/hugegraph-client/src/test/resources/hugegraph-create.properties
@@ -0,0 +1,6 @@
+gremlin.graph=com.baidu.hugegraph.auth.HugeFactoryAuthProxy
+backend=rocksdb
+serializer=binary
+store=hugegraph2
+rocksdb.data_path=./hg2
+rocksdb.wal_path=./hg2
diff --git a/hugegraph-client/src/test/resources/hugegraph.truststore b/hugegraph-client/src/test/resources/hugegraph.truststore
new file mode 100644
index 0000000..5b0828d
--- /dev/null
+++ b/hugegraph-client/src/test/resources/hugegraph.truststore
Binary files differ