diff --git a/src/console/CmdProcessor.cpp b/src/console/CmdProcessor.cpp
index e099c75fef27dfb7ab9c03c16a671b0ea06e0f82..2db6a251e5458ce132cd868843d02962c2480813 100644
--- a/src/console/CmdProcessor.cpp
+++ b/src/console/CmdProcessor.cpp
@@ -176,6 +176,46 @@ void CmdProcessor::calColumnWidths(
                     }
                     break;
                 }
+                case cpp2::ColumnValue::Type::path: {
+                    auto pathValue = col.get_path();
+                    auto entryList = pathValue.get_entry_list();
+                    decltype(entryList.size()) entryIdx = 0;
+                    formats.resize(entryList.size(), "");
+                    widths.resize(entryList.size(), 0);
+                    for (auto &entry : entryList) {
+                        if (entry.getType() ==  cpp2::PathEntry::vertex) {
+                            auto v = entry.get_vertex();
+                            auto id = v.get_id();
+                            size_t idLen = folly::stringPrintf("%ld", id).size();
+                            if (widths[entryIdx] < idLen) {
+                                widths[entryIdx] = idLen;
+                                genFmt = true;
+                            }
+                            if (genFmt) {
+                                formats[entryIdx] =
+                                        folly::stringPrintf(" %%%ldld", idLen);
+                            }
+                        }
+                        if (entry.getType() == cpp2::PathEntry::edge) {
+                            auto e = entry.get_edge();
+                            auto type = e.get_type();
+                            auto ranking = e.get_ranking();
+                            size_t typeLen = folly::stringPrintf("%s", type.c_str()).size();
+                            size_t rankingLen = folly::stringPrintf("%ld", ranking).size();
+                            size_t len = typeLen + rankingLen + 4;
+                            if (widths[entryIdx] < len) {
+                                widths[entryIdx] = len;
+                                genFmt = true;
+                            }
+                            if (genFmt) {
+                                formats[entryIdx] =
+                                    folly::stringPrintf(" <%%%lds,%%%ldld>", typeLen, rankingLen);
+                            }
+                        }
+                        ++entryIdx;
+                    }
+                    break;
+                }
             }
             ++idx;
         }
@@ -317,6 +357,22 @@ void CmdProcessor::printData(const cpp2::ExecutionResponse& resp,
                                       dt.get_microsec());
                     break;
                 }
+                case cpp2::ColumnValue::Type::path: {
+                    auto pathValue = col.get_path();
+                    auto entryList = pathValue.get_entry_list();
+                    cIdx = 0;
+                    for (auto &entry : entryList) {
+                        if (entry.getType() == cpp2::PathEntry::vertex) {
+                            PRINT_FIELD_VALUE(entry.get_vertex().get_id());
+                        }
+                        if (entry.getType() == cpp2::PathEntry::edge) {
+                            auto e = entry.get_edge();
+                            PRINT_FIELD_VALUE(e.get_type().c_str(), e.get_ranking());
+                        }
+                        ++cIdx;
+                    }
+                    break;
+                }
             }
             ++cIdx;
         }
diff --git a/src/daemons/CMakeLists.txt b/src/daemons/CMakeLists.txt
index 621ab31b2dc4f2c3186aefee5aa4a08949d749c8..62b853379089ffadda26767e51ba419e66484cad 100644
--- a/src/daemons/CMakeLists.txt
+++ b/src/daemons/CMakeLists.txt
@@ -29,6 +29,7 @@ nebula_add_executable(
         $<TARGET_OBJECTS:time_obj>
         $<TARGET_OBJECTS:fs_obj>
         $<TARGET_OBJECTS:base_obj>
+        $<TARGET_OBJECTS:concurrent_obj>
     LIBRARIES
         proxygenhttpserver
         proxygenlib
diff --git a/src/executor/CMakeLists.txt b/src/executor/CMakeLists.txt
index 16676818977371ae44c7691a72a98757df667337..f8e7ffffd8f17b019608b2b998a52d3343c47983 100644
--- a/src/executor/CMakeLists.txt
+++ b/src/executor/CMakeLists.txt
@@ -48,6 +48,7 @@ nebula_add_library(
     FindExecutor.cpp
     MatchExecutor.cpp
     DeleteVertexExecutor.cpp
+    FindPathExecutor.cpp
 )
 
 nebula_add_library(
diff --git a/src/executor/Executor.cpp b/src/executor/Executor.cpp
index 5f82aeaa181e83618ee7661015c658fa73ebeb56..abd261c99be3e3c66bdf049bacf9c20d51e75a72 100644
--- a/src/executor/Executor.cpp
+++ b/src/executor/Executor.cpp
@@ -45,6 +45,7 @@
 #include "graph/DeleteVertexExecutor.h"
 #include "graph/UpdateVertexExecutor.h"
 #include "graph/UpdateEdgeExecutor.h"
+#include "graph/FindPathExecutor.h"
 
 namespace nebula {
 namespace graph {
@@ -155,6 +156,9 @@ std::unique_ptr<Executor> Executor::makeExecutor(Sentence *sentence) {
         case Sentence::Kind::kUpdateEdge:
             executor = std::make_unique<UpdateEdgeExecutor>(sentence, ectx());
             break;
+        case Sentence::Kind::kFindPath:
+            executor = std::make_unique<FindPathExecutor>(sentence, ectx());
+            break;
         case Sentence::Kind::kUnknown:
             LOG(FATAL) << "Sentence kind unknown";
             break;
diff --git a/src/executor/FindPathExecutor.cpp b/src/executor/FindPathExecutor.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..49cd9bc78e3bd9a8f72359d00aba95efa0bd771b
--- /dev/null
+++ b/src/executor/FindPathExecutor.cpp
@@ -0,0 +1,709 @@
+/* Copyright (c) 2019 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License,
+ * attached with Common Clause Condition 1.0, found in the LICENSES directory.
+ */
+
+#include "base/Base.h"
+#include "FindPathExecutor.h"
+
+namespace nebula {
+namespace graph {
+
+FindPathExecutor::FindPathExecutor(Sentence *sentence, ExecutionContext *exct)
+        : TraverseExecutor(exct) {
+    sentence_ = static_cast<FindPathSentence*>(sentence);
+}
+
+Status FindPathExecutor::prepare() {
+    Status status;
+    expCtx_ = std::make_unique<ExpressionContext>();
+    do {
+        if (sentence_->from() != nullptr) {
+            status = sentence_->from()->prepare(from_);
+            if (!status.ok()) {
+                break;
+            }
+        }
+        if (sentence_->to() != nullptr) {
+            status = sentence_->to()->prepare(to_);
+            if (!status.ok()) {
+                break;
+            }
+        }
+        if (sentence_->over() != nullptr) {
+            status = sentence_->over()->prepare(over_);
+            if (!status.ok()) {
+                break;
+            }
+        }
+        if (sentence_->step() != nullptr) {
+            status = sentence_->step()->prepare(step_);
+            if (!status.ok()) {
+                break;
+            }
+        }
+        if (sentence_->where() != nullptr) {
+            status = sentence_->where()->prepare(where_);
+            if (!status.ok()) {
+                break;
+            }
+        }
+        shortest_ = sentence_->isShortest();
+    } while (false);
+    return status;
+}
+
+Status FindPathExecutor::beforeExecute() {
+    Status status;
+    do {
+        status = checkIfGraphSpaceChosen();
+        if (!status.ok()) {
+            break;;
+        }
+        spaceId_ = ectx()->rctx()->session()->space();
+
+        status = prepareOver();
+        if (!status.ok()) {
+            break;
+        }
+
+        status = setupVids();
+        if (!status.ok()) {
+            break;
+        }
+    } while (false);
+    return status;
+}
+
+Status FindPathExecutor::prepareOverAll() {
+    auto edgeAllStatus = ectx()->schemaManager()->getAllEdge(spaceId_);
+
+    if (!edgeAllStatus.ok()) {
+        return edgeAllStatus.status();
+    }
+
+    auto allEdge = edgeAllStatus.value();
+    for (auto &e : allEdge) {
+        auto edgeStatus = ectx()->schemaManager()->toEdgeType(spaceId_, e);
+        if (!edgeStatus.ok()) {
+            return edgeStatus.status();
+        }
+
+        auto v = edgeStatus.value();
+        over_.edgeTypes_.emplace_back(v);
+        over_.oppositeTypes_.emplace_back(-v);
+
+        if (!expCtx_->addEdge(e, v)) {
+            return Status::Error(folly::sformat("edge alias({}) was dup", e));
+        }
+        edgeTypeNameMap_.emplace(v, e);
+    }
+
+    return Status::OK();
+}
+
+Status FindPathExecutor::prepareOver() {
+    Status status = Status::OK();
+
+    for (auto e : over_.edges_) {
+        if (e->isOverAll()) {
+            expCtx_->setOverAllEdge();
+            return prepareOverAll();
+        }
+
+        auto edgeStatus = ectx()->schemaManager()->toEdgeType(spaceId_, *e->edge());
+        if (!edgeStatus.ok()) {
+            return edgeStatus.status();
+        }
+
+        auto v = edgeStatus.value();
+        if (e->isReversely()) {
+            over_.edgeTypes_.emplace_back(-v);
+            over_.oppositeTypes_.emplace_back(v);
+        } else {
+            over_.edgeTypes_.emplace_back(v);
+            over_.oppositeTypes_.emplace_back(-v);
+        }
+
+        if (e->alias() != nullptr) {
+            if (!expCtx_->addEdge(*e->alias(), v)) {
+                return Status::Error(folly::sformat("edge alias({}) was dup", *e->alias()));
+            }
+        } else {
+            if (!expCtx_->addEdge(*e->edge(), v)) {
+                return Status::Error(folly::sformat("edge alias({}) was dup", *e->edge()));
+            }
+        }
+
+        edgeTypeNameMap_.emplace(v, *e->edge());
+    }
+
+    return status;
+}
+
+void FindPathExecutor::execute() {
+    DCHECK(onError_);
+    DCHECK(onFinish_);
+
+    auto status = beforeExecute();
+    if (!status.ok()) {
+        onError_(std::move(status));
+        return;
+    }
+
+    steps_ = step_.steps_ / 2 + step_.steps_ % 2;
+    fromVids_ = from_.vids_;
+    toVids_ = to_.vids_;
+    visitedFrom_.insert(fromVids_.begin(), fromVids_.end());
+    visitedTo_.insert(toVids_.begin(), toVids_.end());
+    targetNotFound_.insert(toVids_.begin(), toVids_.end());
+    for (auto &v : fromVids_) {
+        Path path;
+        pathFrom_.emplace(v, std::move(path));
+    }
+    for (auto &v : toVids_) {
+        Path path;
+        pathTo_.emplace(v, std::move(path));
+    }
+
+    getNeighborsAndFindPath();
+}
+
+void FindPathExecutor::getNeighborsAndFindPath() {
+    fPro_ = std::make_unique<folly::Promise<folly::Unit>>();
+    tPro_ = std::make_unique<folly::Promise<folly::Unit>>();
+    std::vector<folly::Future<folly::Unit>> futures;
+    futures.emplace_back(fPro_->getFuture());
+    futures.emplace_back(tPro_->getFuture());
+
+    auto props = getStepOutProps(false);
+    if (!props.ok()) {
+        onError_(std::move(props).status());
+        return;
+    }
+    getFromFrontiers(std::move(props).value());
+
+    props = getStepOutProps(true);
+    if (!props.ok()) {
+        onError_(std::move(props).status());
+        return;
+    }
+    getToFrontiers(std::move(props).value());
+
+    auto *runner = ectx()->rctx()->runner();
+    auto cb = [this] (auto &&result) {
+        UNUSED(result);
+        if (!fStatus_.ok() || !tStatus_.ok()) {
+            std::string msg = fStatus_.toString() + " " + tStatus_.toString();
+            onError_(Status::Error(std::move(msg)));
+            return;
+        }
+
+        findPath();
+    };
+    auto error = [this] (auto &&e) {
+        LOG(ERROR) << "Exception caught: " << e.what();
+        onError_(Status::Error("Internal error."));
+    };
+    folly::collectAll(futures).via(runner).thenValue(cb).thenError(error);
+}
+
+void FindPathExecutor::findPath() {
+    VLOG(2) << "Find Path.";
+    visitedFrom_.clear();
+    std::multimap<VertexID, Path> pathF;
+    for (auto &frontier : fromFrontiers_.second) {
+        // Notice: we treat edges with different ranking
+        // between two vertices as different path
+        for (auto &neighbor : frontier.second) {
+            auto dstId = std::get<0>(neighbor);
+            VLOG(2) << "src vertex:" << frontier.first;
+            VLOG(2) << "dst vertex:" << dstId;
+            // if frontiers of F are neighbors of visitedByT,
+            // we found an odd path
+            if (visitedTo_.count(dstId) == 1) {
+                meetOddPath(frontier.first, dstId, neighbor);
+            }
+
+            // update the path to frontiers
+            updatePath(frontier.first, pathFrom_, neighbor, pathF, VisitedBy::FROM);
+            visitedFrom_.emplace(dstId);
+        }  // for `neighbor'
+    }  // for `frontier'
+    pathFrom_ = std::move(pathF);
+    fromVids_.clear();
+    fromVids_.reserve(visitedFrom_.size());
+    std::copy(visitedFrom_.begin(),
+              visitedFrom_.end(), std::back_inserter(fromVids_));
+
+    visitedTo_.clear();
+    std::multimap<VertexID, Path> pathT;
+    for (auto &frontier : toFrontiers_.second) {
+        for (auto &neighbor : frontier.second) {
+            auto dstId = std::get<0>(neighbor);
+            // update the path to frontiers
+            updatePath(frontier.first, pathTo_, neighbor, pathT, VisitedBy::TO);
+            visitedTo_.emplace(dstId);
+        }  // for `neighbor'
+    }  // for `frontier'
+    pathTo_ = std::move(pathT);
+    toVids_.clear();
+    toVids_.reserve(visitedTo_.size());
+    std::copy(visitedTo_.begin(),
+              visitedTo_.end(), std::back_inserter(toVids_));
+
+    std::sort(fromVids_.begin(), fromVids_.end());
+    std::sort(toVids_.begin(), toVids_.end());
+    std::set<VertexID> intersect;
+    std::set_intersection(fromVids_.begin(), fromVids_.end(),
+                          toVids_.begin(), toVids_.end(),
+                          std::inserter(intersect, intersect.end()));
+    // if frontiersF meets frontiersT, we found an even path
+    if (!intersect.empty()) {
+        if (shortest_ && targetNotFound_.empty()) {
+            onFinish_();
+            return;
+        }
+        for (auto intersectId : intersect) {
+            meetEvenPath(intersectId);
+        }  // `intersectId'
+    }
+
+    if (isFinalStep() ||
+         (shortest_ && targetNotFound_.empty())) {
+        onFinish_();
+        return;
+    } else {
+        LOG(INFO) << "Current step:" << currentStep_;
+        ++currentStep_;
+    }
+    getNeighborsAndFindPath();
+}
+
+inline void FindPathExecutor::meetOddPath(VertexID src, VertexID dst, Neighbor &neighbor) {
+    VLOG(2) << "Meet Odd Path.";
+    auto rangeF = pathFrom_.equal_range(src);
+    for (auto i = rangeF.first; i != rangeF.second; ++i) {
+        auto rangeT = pathTo_.equal_range(dst);
+        for (auto j = rangeT.first; j != rangeT.second; ++j) {
+            if (j->second.size() + i->second.size() > step_.steps_) {
+                continue;
+            }
+            // Build path:
+            // i->second + (src,type,ranking) + (dst, -type, ranking) + j->second
+            Path path = i->second;
+            auto s0 = std::make_unique<StepOut>(neighbor);
+            std::get<0>(*s0) = src;
+            path.emplace_back(s0.get());
+            stepOutHolder_.emplace(std::move(s0));
+            VLOG(2) << "PathF: " << buildPathString(path);
+
+            auto s1 = std::make_unique<StepOut>(neighbor);
+            std::get<1>(*s1) = - std::get<1>(neighbor);
+            path.emplace_back(s1.get());
+            stepOutHolder_.emplace(std::move(s1));
+            path.insert(path.end(), j->second.begin(), j->second.end());
+            VLOG(2) << "PathT: " << buildPathString(j->second);
+
+            auto target = std::get<0>(*(path.back()));
+            if (shortest_) {
+                targetNotFound_.erase(target);
+                if (finalPath_.count(target) > 0) {
+                    // already found a shorter path
+                    continue;
+                } else {
+                    VLOG(2) << "Found path: " << buildPathString(path);
+                    finalPath_.emplace(target, std::move(path));
+                }
+            } else {
+                VLOG(2) << "Found path: " << buildPathString(path);
+                finalPath_.emplace(target, std::move(path));
+            }
+        }  // for `j'
+    }  // for `i'
+}
+
+inline void FindPathExecutor::meetEvenPath(VertexID intersectId) {
+    VLOG(2) << "Meet Even Path.";
+    auto rangeF = pathFrom_.equal_range(intersectId);
+    auto rangeT = pathTo_.equal_range(intersectId);
+    for (auto i = rangeF.first; i != rangeF.second; ++i) {
+        for (auto j = rangeT.first; j != rangeT.second; ++j) {
+            if (j->second.size() + i->second.size() > step_.steps_) {
+                continue;
+            }
+            // Build path:
+            // i->second + (src,type,ranking) + j->second
+            Path path = i->second;
+            VLOG(2) << "PathF: " << buildPathString(path);
+            if (j->second.size() > 0) {
+                StepOut *s = j->second.front();
+                auto s0 = std::make_unique<StepOut>(*s);
+                std::get<0>(*s0) = intersectId;
+                std::get<1>(*s0) = - std::get<1>(*s0);
+                path.emplace_back(s0.get());
+                stepOutHolder_.emplace(std::move(s0));
+                VLOG(2) << "Joiner: " << buildPathString(path);
+            } else if (i->second.size() > 0) {
+                StepOut *s = i->second.back();
+                auto s0 = std::make_unique<StepOut>(*s);
+                std::get<0>(*s0) = intersectId;
+                path.emplace_back(s0.get());
+                stepOutHolder_.emplace(std::move(s0));
+                VLOG(2) << "Joiner: " << buildPathString(path);
+            }
+            VLOG(2) << "PathT: " << buildPathString(j->second);
+            path.insert(path.end(), j->second.begin(), j->second.end());
+            auto target = std::get<0>(*(path.back()));
+            if (shortest_) {
+                if (finalPath_.count(target) > 0) {
+                    // already found a shorter path
+                    continue;
+                } else {
+                    targetNotFound_.erase(target);
+                    VLOG(2) << "Found path: " << buildPathString(path);
+                    finalPath_.emplace(target, std::move(path));
+                }
+            } else {
+                VLOG(2) << "Found path: " << buildPathString(path);
+                finalPath_.emplace(target, std::move(path));
+            }
+        }
+    }
+}
+
+inline void FindPathExecutor::updatePath(
+            VertexID &src,
+            std::multimap<VertexID, Path> &pathToSrc,
+            Neighbor &neighbor,
+            std::multimap<VertexID, Path> &pathToNeighbor,
+            VisitedBy visitedBy) {
+    VLOG(2) << "Update Path.";
+    auto range = pathToSrc.equal_range(src);
+    for (auto i = range.first; i != range.second; ++i) {
+        // Build path:
+        // i->second + (src,type,ranking)
+        Path path = i->second;
+        VLOG(2) << "Interim path before :" << buildPathString(path);
+        VLOG(2) << "Interim path length before:" << path.size();
+        auto s = std::make_unique<StepOut>(neighbor);
+        std::get<0>(*s) = src;
+        if (visitedBy == VisitedBy::FROM) {
+            path.emplace_back(s.get());
+        } else {
+            path.emplace(path.begin(), s.get());
+        }
+        VLOG(2) << "Neighbor: " << std::get<0>(neighbor);
+        VLOG(2) << "Interim path:" << buildPathString(path);
+        VLOG(2) << "Interim path length:" << path.size();
+        stepOutHolder_.emplace(std::move(s));
+        pathToNeighbor.emplace(std::get<0>(neighbor), std::move(path));
+    }  // for `i'
+}
+
+Status FindPathExecutor::setupVids() {
+    Status status = Status::OK();
+    do {
+        if (sentence_->from()->isRef()) {
+            status = setupVidsFromRef(from_);
+            if (!status.ok()) {
+                break;
+            }
+        }
+
+        if (sentence_->to()->isRef()) {
+            status = setupVidsFromRef(to_);
+            if (!status.ok()) {
+                break;
+            }
+        }
+    } while (false);
+
+    return status;
+}
+
+Status FindPathExecutor::setupVidsFromRef(Clause::Vertices &vertices) {
+    const InterimResult *inputs;
+    if (vertices.varname_ == nullptr) {
+        inputs = inputs_.get();
+        if (inputs == nullptr) {
+            return Status::OK();
+        }
+    } else {
+        inputs = ectx()->variableHolder()->get(*(vertices.varname_));
+        if (inputs == nullptr) {
+            return Status::Error("Variable `%s' not defined", vertices.varname_->c_str());
+        }
+    }
+
+    auto result = inputs->getDistinctVIDs(*(vertices.colname_));
+    if (!result.ok()) {
+        return std::move(result).status();
+    }
+    vertices.vids_ = std::move(result).value();
+    return Status::OK();
+}
+
+void FindPathExecutor::getFromFrontiers(
+        std::vector<storage::cpp2::PropDef> props) {
+    auto future = ectx()->getStorageClient()->getNeighbors(spaceId_,
+                                                           std::move(fromVids_),
+                                                           over_.edgeTypes_,
+                                                           "",
+                                                           std::move(props));
+    auto *runner = ectx()->rctx()->runner();
+    auto cb = [this] (auto &&result) {
+        Frontiers frontiers;
+        auto completeness = result.completeness();
+        if (completeness == 0) {
+            fStatus_ = Status::Error("Get neighbors failed.");
+            fPro_->setValue();
+            return;
+        } else if (completeness != 100) {
+            LOG(INFO) << "Get neighbors partially failed: "  << completeness << "%";
+            for (auto &error : result.failedParts()) {
+                LOG(ERROR) << "part: " << error.first
+                           << "error code: " << static_cast<int>(error.second);
+            }
+        }
+        auto status = doFilter(std::move(result), where_.filter_, true, frontiers);
+        if (!status.ok()) {
+            fStatus_ = std::move(status);
+            fPro_->setValue();
+            return;
+        }
+        fromFrontiers_ = std::make_pair(VisitedBy::FROM, std::move(frontiers));
+        fPro_->setValue();
+    };
+    auto error = [this] (auto &&e) {
+        LOG(ERROR) << "Exception caught: " << e.what();
+        fStatus_ = Status::Error("Get neighbors failed.");
+    };
+    std::move(future).via(runner, folly::Executor::HI_PRI).thenValue(cb).thenError(error);
+}
+
+void FindPathExecutor::getToFrontiers(
+        std::vector<storage::cpp2::PropDef> props) {
+    auto future = ectx()->getStorageClient()->getNeighbors(spaceId_,
+                                                           std::move(toVids_),
+                                                           over_.oppositeTypes_,
+                                                           "",
+                                                           std::move(props));
+    auto *runner = ectx()->rctx()->runner();
+    auto cb = [this] (auto &&result) {
+        Frontiers frontiers;
+        auto completeness = result.completeness();
+        if (completeness == 0) {
+            tStatus_ = Status::Error("Get neighbors failed.");
+            tPro_->setValue();
+            return;
+        } else if (completeness != 100) {
+            LOG(INFO) << "Get neighbors partially failed: "  << completeness << "%";
+            for (auto &error : result.failedParts()) {
+                LOG(ERROR) << "part: " << error.first
+                           << "error code: " << static_cast<int>(error.second);
+            }
+        }
+        auto status = doFilter(std::move(result), where_.filter_, false, frontiers);
+        if (!status.ok()) {
+            tStatus_ = std::move(status);
+            tPro_->setValue();
+            return;
+        }
+        toFrontiers_ = std::make_pair(VisitedBy::TO, std::move(frontiers));
+        tPro_->setValue();
+    };
+    auto error = [this] (auto &&e) {
+        LOG(ERROR) << "Exception caught: " << e.what();
+        tStatus_ = Status::Error("Get neighbors failed.");
+    };
+    std::move(future).via(runner, folly::Executor::HI_PRI).thenValue(cb).thenError(error);
+}
+
+Status FindPathExecutor::doFilter(
+        storage::StorageRpcResponse<storage::cpp2::QueryResponse> &&result,
+        Expression *filter,
+        bool isOutBound,
+        Frontiers &frontiers) {
+    UNUSED(filter);
+    UNUSED(isOutBound);
+
+    auto &resps = result.responses();
+    for (auto &resp : resps) {
+        if (resp.get_vertices() == nullptr) {
+            continue;
+        }
+
+        std::unordered_map<EdgeType, std::shared_ptr<ResultSchemaProvider>> edgeSchema;
+        auto *eschema = resp.get_edge_schema();
+        if (eschema != nullptr) {
+            std::transform(eschema->cbegin(), eschema->cend(),
+                           std::inserter(edgeSchema, edgeSchema.begin()), [](auto &schema) {
+                               return std::make_pair(
+                                   schema.first,
+                                   std::make_shared<ResultSchemaProvider>(schema.second));
+                           });
+        }
+
+        if (edgeSchema.empty()) {
+            continue;
+        }
+
+        for (auto &vdata : resp.vertices) {
+            DCHECK(vdata.__isset.edge_data);
+            for (auto &edata : vdata.edge_data) {
+                auto edgeType = edata.type;
+                auto it = edgeSchema.find(edgeType);
+                DCHECK(it != edgeSchema.end());
+                RowSetReader rsReader(it->second, edata.data);
+                auto iter = rsReader.begin();
+                Neighbors neighbors;
+                while (iter) {
+                    std::vector<VariantType> temps;
+                    for (auto &prop : kReserveProps_) {
+                        auto res = RowReader::getPropByName(&*iter, prop);
+                        if (ok(res)) {
+                            temps.emplace_back(std::move(value(res)));
+                        } else {
+                            return Status::Error("get edge prop failed %s", prop.c_str());
+                        }
+                    }
+                    Neighbor neighbor(
+                        boost::get<int64_t>(temps[0]),
+                        boost::get<int64_t>(temps[1]),
+                        boost::get<int64_t>(temps[2]));
+                    neighbors.emplace_back(std::move(neighbor));
+                    ++iter;
+                }  // while `iter'
+                auto frontier = std::make_pair(vdata.get_vertex_id(), std::move(neighbors));
+                frontiers.emplace_back(std::move(frontier));
+            }  // `edata'
+        }  // for `vdata'
+    }  // for `resp'
+    return Status::OK();
+}
+
+StatusOr<std::vector<storage::cpp2::PropDef>>
+FindPathExecutor::getStepOutProps(bool reversely) {
+    auto *edges = &over_.edgeTypes_;
+    if (reversely) {
+        edges = &over_.oppositeTypes_;
+    }
+    std::vector<storage::cpp2::PropDef> props;
+    for (auto &e : *edges) {
+        for (auto &prop : kReserveProps_) {
+            storage::cpp2::PropDef pd;
+            pd.owner = storage::cpp2::PropOwner::EDGE;
+            pd.name = prop;
+            pd.id.set_edge_type(e);
+            props.emplace_back(std::move(pd));
+        }
+    }
+
+    return props;
+}
+
+std::string FindPathExecutor::buildPathString(const Path &path) {
+    std::string pathStr;
+    auto iter = path.begin();
+    for (; iter != path.end(); ++iter) {
+        auto *step = *iter;
+        auto id = std::get<0>(*step);
+        auto type = std::get<1>(*step);
+        auto ranking = std::get<2>(*step);
+        if (type < 0) {
+            pathStr += folly::to<std::string>(id);
+            ++iter;
+            break;
+        }
+
+        pathStr += folly::stringPrintf("%ld<%d,%ld>", id, type, ranking);
+    }
+
+    for (; iter != path.end(); ++iter) {
+        auto *step = *iter;
+        auto id = std::get<0>(*step);
+        auto type = std::get<1>(*step);
+        auto ranking = std::get<2>(*step);
+
+        pathStr += folly::stringPrintf("<%d,%ld>%ld", -type, ranking, id);
+    }
+
+    return pathStr;
+}
+
+cpp2::RowValue FindPathExecutor::buildPathRow(const Path &path) {
+    cpp2::RowValue rowValue;
+    std::vector<cpp2::ColumnValue> row;
+    cpp2::Path pathValue;
+    auto entryList = pathValue.get_entry_list();
+    auto iter = path.begin();
+    for (; iter != path.end(); ++iter) {
+        auto *step = *iter;
+        auto id = std::get<0>(*step);
+        auto type = std::get<1>(*step);
+        auto ranking = std::get<2>(*step);
+        if (type < 0) {
+            entryList.emplace_back();
+            cpp2::Vertex vertex;
+            vertex.set_id(id);
+            entryList.back().set_vertex(std::move(vertex));
+            ++iter;
+            break;
+        }
+        entryList.emplace_back();
+        cpp2::Vertex vertex;
+        vertex.set_id(id);
+        entryList.back().set_vertex(std::move(vertex));
+
+        entryList.emplace_back();
+        cpp2::Edge edge;
+        auto typeName = edgeTypeNameMap_.find(type);
+        DCHECK(typeName != edgeTypeNameMap_.end()) << type;
+        edge.set_type(typeName->second);
+        edge.set_ranking(ranking);
+        entryList.back().set_edge(std::move(edge));
+    }
+
+    for (; iter != path.end(); ++iter) {
+        auto *step = *iter;
+        auto id = std::get<0>(*step);
+        auto type = std::get<1>(*step);
+        auto ranking = std::get<2>(*step);
+
+        entryList.emplace_back();
+        cpp2::Edge edge;
+        auto typeName = edgeTypeNameMap_.find(-type);
+        DCHECK(typeName != edgeTypeNameMap_.end()) << type;
+        edge.set_type(typeName->second);
+        edge.set_ranking(ranking);
+        entryList.back().set_edge(std::move(edge));
+
+        entryList.emplace_back();
+        cpp2::Vertex vertex;
+        vertex.set_id(id);
+        entryList.back().set_vertex(std::move(vertex));
+    }
+
+    row.emplace_back();
+    pathValue.set_entry_list(std::move(entryList));
+    row.back().set_path(std::move(pathValue));
+    rowValue.set_columns(std::move(row));
+    return rowValue;
+}
+
+void FindPathExecutor::setupResponse(cpp2::ExecutionResponse &resp) {
+    std::vector<cpp2::RowValue> rows;
+    for (auto &path : finalPath_) {
+        auto row = buildPathRow(path.second);
+        rows.emplace_back(std::move(row));
+        VLOG(1) << "Path: " << buildPathString(path.second);
+    }
+
+    std::vector<std::string> colNames = {"_path_"};
+    resp.set_column_names(std::move(colNames));
+    resp.set_rows(std::move(rows));
+}
+}  // namespace graph
+}  // namespace nebula
diff --git a/src/executor/FindPathExecutor.h b/src/executor/FindPathExecutor.h
new file mode 100644
index 0000000000000000000000000000000000000000..87aa250ec3a85ced1e5bfbcd894176dfa0929e1c
--- /dev/null
+++ b/src/executor/FindPathExecutor.h
@@ -0,0 +1,145 @@
+/* Copyright (c) 2019 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License,
+ * attached with Common Clause Condition 1.0, found in the LICENSES directory.
+ */
+
+#ifndef GRAPH_FINDPATHEXECUTOR_H_
+#define GRAPH_FINDPATHEXECUTOR_H_
+
+#include "base/Base.h"
+#include "graph/TraverseExecutor.h"
+#include "storage/client/StorageClient.h"
+#include "common/concurrent/Barrier.h"
+
+namespace nebula {
+namespace graph {
+
+using SchemaProps = std::unordered_map<std::string, std::vector<std::string>>;
+const std::vector<std::string> kReserveProps_ = {"_dst", "_type", "_rank"};
+using Neighbor = std::tuple<VertexID, EdgeType, EdgeRanking>; /* dst, type, rank*/
+using Neighbors = std::vector<Neighbor>;
+using Frontiers =
+        std::vector<
+                    std::pair<
+                              VertexID, /* start */
+                              Neighbors /* frontiers of vertex*/
+                             >
+                   >;
+
+using StepOut = std::tuple<VertexID, EdgeType, EdgeRanking>; /* src, type, rank*/
+using Path = std::list<StepOut*>;
+enum class VisitedBy : char {
+    FROM,
+    TO,
+};
+
+class FindPathExecutor final : public TraverseExecutor {
+public:
+    FindPathExecutor(Sentence *sentence, ExecutionContext *ectx);
+
+    const char* name() const override {
+        return "FindPathExecutor";
+    }
+
+    Status MUST_USE_RESULT prepare() override;
+
+    void execute() override;
+
+    void feedResult(std::unique_ptr<InterimResult> result) override {
+        inputs_ = std::move(result);
+    }
+
+    void setupResponse(cpp2::ExecutionResponse &resp) override;
+
+    static std::string buildPathString(const Path &path);
+
+    cpp2::RowValue buildPathRow(const Path &path);
+
+private:
+    // Do some prepare work that can not do in prepare()
+    Status beforeExecute();
+
+    Status prepareOver();
+
+    Status prepareOverAll();
+
+    void getNeighborsAndFindPath();
+
+    bool isFinalStep() {
+        return currentStep_ == steps_;
+    }
+
+    void getFromFrontiers(std::vector<storage::cpp2::PropDef> props);
+
+    void getToFrontiers(std::vector<storage::cpp2::PropDef> props);
+
+    void findPath();
+
+    inline void meetOddPath(VertexID src, VertexID dst, Neighbor &neighbor);
+
+    inline void meetEvenPath(VertexID intersectId);
+
+    inline void updatePath(
+            VertexID &src,
+            std::multimap<VertexID, Path> &pathToSrc,
+            Neighbor &neighbor,
+            std::multimap<VertexID, Path> &pathToNeighbor,
+            VisitedBy visitedBy);
+
+    Status setupVids();
+
+    Status setupVidsFromRef(Clause::Vertices &vertices);
+
+    Status doFilter(
+            storage::StorageRpcResponse<storage::cpp2::QueryResponse> &&result,
+            Expression *filter,
+            bool isOutBound,
+            Frontiers &frontiers);
+
+    StatusOr<std::vector<storage::cpp2::PropDef>> getStepOutProps(bool reversely);
+
+    StatusOr<std::vector<storage::cpp2::PropDef>> getDstProps();
+
+private:
+    FindPathSentence                               *sentence_{nullptr};
+    std::unique_ptr<ExpressionContext>              expCtx_;
+    GraphSpaceID                                    spaceId_{INT_MIN};
+    Clause::Vertices                                from_;
+    Clause::Vertices                                to_;
+    Clause::Over                                    over_;
+    Clause::Step                                    step_;
+    Clause::Where                                   where_;
+    bool                                            shortest_{false};
+    std::unique_ptr<InterimResult>                  inputs_;
+    using SchemaPropIndex = std::unordered_map<std::pair<std::string, std::string>, int64_t>;
+    SchemaPropIndex                                 srcTagProps_;
+    SchemaPropIndex                                 dstTagProps_;
+    std::unordered_map<EdgeType, std::string>       edgeTypeNameMap_;
+    std::unique_ptr<folly::Promise<folly::Unit>>    fPro_;
+    std::unique_ptr<folly::Promise<folly::Unit>>    tPro_;
+    Status                                          fStatus_;
+    Status                                          tStatus_;
+    std::unordered_set<VertexID>                    targetNotFound_;
+    using StepOutHolder = std::unordered_set<std::unique_ptr<StepOut>>;
+    StepOutHolder                                   stepOutHolder_;
+    // next step starting vertices
+    std::unordered_set<VertexID>                    visitedFrom_;
+    std::unordered_set<VertexID>                    visitedTo_;
+    // next step starting vertices
+    std::vector<VertexID>                           fromVids_;
+    std::vector<VertexID>                           toVids_;
+    // frontiers of vertices
+    std::pair<VisitedBy, Frontiers>                 fromFrontiers_;
+    std::pair<VisitedBy, Frontiers>                 toFrontiers_;
+    // interim path
+    std::multimap<VertexID, Path>                   pathFrom_;
+    std::multimap<VertexID, Path>                   pathTo_;
+    // final path(shortest or all)
+    std::multimap<VertexID, Path>                   finalPath_;
+    uint64_t                                        currentStep_{1};
+    uint64_t                                        steps_{0};
+};
+}  // namespace graph
+}  // namespace nebula
+#endif
diff --git a/src/executor/PipeExecutor.cpp b/src/executor/PipeExecutor.cpp
index 5bb66904acd190e2d9baefc52bd1556314d4d892..d47c7f079995f8d529e6c7323b9b53e14df97ea4 100644
--- a/src/executor/PipeExecutor.cpp
+++ b/src/executor/PipeExecutor.cpp
@@ -104,6 +104,10 @@ Status PipeExecutor::syntaxPreCheck() {
         return Status::SyntaxError("Set op not support input.");
     }
 
+    if (sentence_->left()->kind() == Sentence::Kind::kFindPath) {
+        return Status::SyntaxError("Can not reference the result of FindPath.");
+    }
+
     return Status::OK();
 }
 
diff --git a/src/executor/TraverseExecutor.cpp b/src/executor/TraverseExecutor.cpp
index 51a00d10d54098d1690611b273a61fda5573d6b0..e3f8211b6c3fd91cb40762ee2311b020fc89b461 100644
--- a/src/executor/TraverseExecutor.cpp
+++ b/src/executor/TraverseExecutor.cpp
@@ -17,6 +17,7 @@
 #include "graph/SetExecutor.h"
 #include "graph/FindExecutor.h"
 #include "graph/MatchExecutor.h"
+#include "graph/FindPathExecutor.h"
 
 namespace nebula {
 namespace graph {
@@ -56,6 +57,9 @@ TraverseExecutor::makeTraverseExecutor(Sentence *sentence, ExecutionContext *ect
         case Sentence::Kind::kFind:
             executor = std::make_unique<FindExecutor>(sentence, ectx);
             break;
+        case Sentence::Kind::kFindPath:
+            executor = std::make_unique<FindPathExecutor>(sentence, ectx);
+            break;
         case Sentence::Kind::kUnknown:
             LOG(FATAL) << "Sentence kind unknown";
             break;
diff --git a/src/executor/test/CMakeLists.txt b/src/executor/test/CMakeLists.txt
index dd02a13a6f885d8f8b436c895a7903d5e54ba804..b0feeb6a0268ae91808cc699f15368770aca268c 100644
--- a/src/executor/test/CMakeLists.txt
+++ b/src/executor/test/CMakeLists.txt
@@ -234,3 +234,19 @@ nebula_add_test(
         gtest
 )
 
+nebula_add_test(
+    NAME
+        find_path_test
+    SOURCES
+        FindPathTest.cpp
+    OBJECTS
+        $<TARGET_OBJECTS:graph_test_common_obj>
+        $<TARGET_OBJECTS:client_cpp_obj>
+        $<TARGET_OBJECTS:adHocSchema_obj>
+        ${GRAPH_TEST_LIBS}
+    LIBRARIES
+        ${THRIFT_LIBRARIES}
+        ${ROCKSDB_LIBRARIES}
+        wangle
+        gtest
+)
diff --git a/src/executor/test/FindPathTest.cpp b/src/executor/test/FindPathTest.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..6bc6d6f281499fd6549f6b2b4078c879423281d9
--- /dev/null
+++ b/src/executor/test/FindPathTest.cpp
@@ -0,0 +1,441 @@
+/* Copyright (c) 2019 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License,
+ * attached with Common Clause Condition 1.0, found in the LICENSES directory.
+ */
+
+#include "base/Base.h"
+#include "graph/test/TestEnv.h"
+#include "graph/test/TestBase.h"
+#include "graph/test/TraverseTestBase.h"
+#include "meta/test/TestUtils.h"
+
+namespace nebula {
+namespace graph {
+
+class FindPathTest : public TraverseTestBase {
+protected:
+    void SetUp() override {
+        TraverseTestBase::SetUp();
+    }
+
+    void TearDown() override {
+        TraverseTestBase::TearDown();
+    }
+};
+
+TEST_F(FindPathTest, singleEdgeShortest) {
+    {
+        cpp2::ExecutionResponse resp;
+        auto *fmt = "FIND SHORTEST PATH FROM %ld TO %ld OVER like UPTO 5 STEPS";
+        auto &tim = players_["Tim Duncan"];
+        auto &tony = players_["Tony Parker"];
+        auto query = folly::stringPrintf(fmt, tim.vid(), tony.vid());
+        auto code = client_->execute(query, resp);
+        ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code) << *(resp.get_error_msg());
+        std::vector<std::string> expected = {
+            "5662213458193308137<like,0>-7579316172763586624",
+        };
+        ASSERT_TRUE(verifyPath(resp, expected));
+    }
+    {
+        cpp2::ExecutionResponse resp;
+        auto *fmt = "FIND SHORTEST PATH FROM %ld TO %ld,%ld OVER like UPTO 5 STEPS";
+        auto &tim = players_["Tim Duncan"];
+        auto &tony = players_["Tony Parker"];
+        auto &manu = players_["Manu Ginobili"];
+        auto query = folly::stringPrintf(fmt, tim.vid(), tony.vid(), manu.vid());
+        auto code = client_->execute(query, resp);
+        ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code) << *(resp.get_error_msg());
+        std::vector<std::string> expected = {
+            "5662213458193308137<like,0>3394245602834314645",
+            "5662213458193308137<like,0>-7579316172763586624"
+        };
+        ASSERT_TRUE(verifyPath(resp, expected));
+    }
+    {
+        cpp2::ExecutionResponse resp;
+        auto *fmt = "FIND SHORTEST PATH FROM %ld TO %ld OVER like UPTO 5 STEPS";
+        auto &tim = players_["Tim Duncan"];
+        auto &al = players_["LaMarcus Aldridge"];
+        auto query = folly::stringPrintf(fmt, tim.vid(), al.vid());
+        auto code = client_->execute(query, resp);
+        ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code) << *(resp.get_error_msg());
+        std::vector<std::string> expected = {
+            "5662213458193308137<like,0>-7579316172763586624<like,0>-1782445125509592239"
+        };
+        ASSERT_TRUE(verifyPath(resp, expected));
+    }
+    {
+        cpp2::ExecutionResponse resp;
+        auto *fmt = "FIND SHORTEST PATH FROM %ld TO %ld,%ld,%ld OVER like UPTO 5 STEPS";
+        auto &tim = players_["Tim Duncan"];
+        auto &tony = players_["Tony Parker"];
+        auto &manu = players_["Manu Ginobili"];
+        auto &al = players_["LaMarcus Aldridge"];
+        auto query = folly::stringPrintf(fmt, tim.vid(), tony.vid(), manu.vid(), al.vid());
+        auto code = client_->execute(query, resp);
+        ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code) << *(resp.get_error_msg());
+        std::vector<std::string> expected = {
+            "5662213458193308137<like,0>3394245602834314645",
+            "5662213458193308137<like,0>-7579316172763586624",
+            "5662213458193308137<like,0>-7579316172763586624<like,0>-1782445125509592239"
+        };
+        ASSERT_TRUE(verifyPath(resp, expected));
+    }
+    {
+        cpp2::ExecutionResponse resp;
+        auto *fmt = "FIND SHORTEST PATH FROM %ld TO %ld OVER like UPTO 5 STEPS";
+        auto &tiago = players_["Tiago Splitter"];
+        auto &al = players_["LaMarcus Aldridge"];
+        auto query = folly::stringPrintf(fmt, tiago.vid(), al.vid());
+        auto code = client_->execute(query, resp);
+        ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code) << *(resp.get_error_msg());
+        std::vector<std::string> expected = {
+            "-8160811731890648949<like,0>5662213458193308137<like,0>-7579316172763586624"
+                "<like,0>-1782445125509592239"
+        };
+        ASSERT_TRUE(verifyPath(resp, expected));
+    }
+    {
+        // we only find the shortest path to the dest,
+        // so -8160811731890648949 to -1782445125509592239 is not in result
+        cpp2::ExecutionResponse resp;
+        auto *fmt = "FIND SHORTEST PATH FROM %ld,%ld TO %ld,%ld,%ld OVER like UPTO 5 STEPS";
+        auto &tim = players_["Tim Duncan"];
+        auto &tiago = players_["Tiago Splitter"];
+        auto &tony = players_["Tony Parker"];
+        auto &manu = players_["Manu Ginobili"];
+        auto &al = players_["LaMarcus Aldridge"];
+        auto query = folly::stringPrintf(fmt, tim.vid(), tiago.vid(),
+                tony.vid(), manu.vid(), al.vid());
+        auto code = client_->execute(query, resp);
+        ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code) << *(resp.get_error_msg());
+        std::vector<std::string> expected = {
+            "5662213458193308137<like,0>3394245602834314645",
+            "5662213458193308137<like,0>-7579316172763586624",
+            "5662213458193308137<like,0>-7579316172763586624<like,0>-1782445125509592239"
+        };
+        ASSERT_TRUE(verifyPath(resp, expected));
+    }
+    {
+        cpp2::ExecutionResponse resp;
+        auto *fmt = "GO FROM %ld OVER like yield like._src AS src, like._dst AS dst | "
+                    "FIND SHORTEST PATH FROM $-.src TO $-.dst OVER like UPTO 5 STEPS";
+        auto &tim = players_["Tim Duncan"];
+        auto query = folly::stringPrintf(fmt, tim.vid());
+        auto code = client_->execute(query, resp);
+        ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code) << *(resp.get_error_msg());
+        std::vector<std::string> expected = {
+            "5662213458193308137<like,0>3394245602834314645",
+            "5662213458193308137<like,0>-7579316172763586624"
+        };
+        ASSERT_TRUE(verifyPath(resp, expected));
+    }
+    {
+        cpp2::ExecutionResponse resp;
+        auto *fmt = "$var = GO FROM %ld OVER like yield like._src AS src, like._dst AS dst;"
+                    "FIND SHORTEST PATH FROM $var.src TO $var.dst OVER like UPTO 5 STEPS";
+        auto &tim = players_["Tim Duncan"];
+        auto query = folly::stringPrintf(fmt, tim.vid());
+        auto code = client_->execute(query, resp);
+        ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code) << *(resp.get_error_msg());
+        std::vector<std::string> expected = {
+            "5662213458193308137<like,0>3394245602834314645",
+            "5662213458193308137<like,0>-7579316172763586624"
+        };
+        ASSERT_TRUE(verifyPath(resp, expected));
+    }
+    {
+        cpp2::ExecutionResponse resp;
+        auto *fmt = "$var = GO FROM %ld OVER like yield like._src AS src;"
+                    "GO FROM %ld OVER like yield like._src AS src, like._dst AS dst | "
+                    "FIND SHORTEST PATH FROM $var.src TO $-.dst OVER like UPTO 5 STEPS";
+        auto &tim = players_["Tim Duncan"];
+        auto query = folly::stringPrintf(fmt, tim.vid(), tim.vid());
+        auto code = client_->execute(query, resp);
+        ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code) << *(resp.get_error_msg());
+        std::vector<std::string> expected = {
+            "5662213458193308137<like,0>3394245602834314645",
+            "5662213458193308137<like,0>-7579316172763586624"
+        };
+        ASSERT_TRUE(verifyPath(resp, expected));
+    }
+}
+
+TEST_F(FindPathTest, singleEdgeAll) {
+    /*
+     * TODO: There might exist loops when find all path,
+     * we should provide users with an option on whether or not a loop is required.
+     */
+    {
+        cpp2::ExecutionResponse resp;
+        auto *fmt = "FIND ALL PATH FROM %ld TO %ld OVER like UPTO 3 STEPS";
+        auto &tim = players_["Tim Duncan"];
+        auto &tony = players_["Tony Parker"];
+        auto query = folly::stringPrintf(fmt, tim.vid(), tony.vid());
+        auto code = client_->execute(query, resp);
+        ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code) << *(resp.get_error_msg());
+        std::vector<std::string> expected = {
+            "5662213458193308137<like,0>-7579316172763586624",
+            "5662213458193308137<like,0>-7579316172763586624<like,0>-1782445125509592239"
+                "<like,0>-7579316172763586624",
+            "5662213458193308137<like,0>-7579316172763586624<like,0>5662213458193308137"
+                "<like,0>-7579316172763586624",
+            "5662213458193308137<like,0>3394245602834314645<like,0>5662213458193308137"
+                "<like,0>-7579316172763586624"
+        };
+        ASSERT_TRUE(verifyPath(resp, expected));
+    }
+    {
+        cpp2::ExecutionResponse resp;
+        auto *fmt = "FIND ALL PATH FROM %ld TO %ld,%ld OVER like UPTO 3 STEPS";
+        auto &tim = players_["Tim Duncan"];
+        auto &tony = players_["Tony Parker"];
+        auto &manu = players_["Manu Ginobili"];
+        auto query = folly::stringPrintf(fmt, tim.vid(), tony.vid(), manu.vid());
+        auto code = client_->execute(query, resp);
+        ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code) << *(resp.get_error_msg());
+        std::vector<std::string> expected = {
+            "5662213458193308137<like,0>-7579316172763586624",
+            "5662213458193308137<like,0>-7579316172763586624<like,0>-1782445125509592239"
+                "<like,0>-7579316172763586624",
+            "5662213458193308137<like,0>-7579316172763586624<like,0>5662213458193308137"
+                "<like,0>-7579316172763586624",
+            "5662213458193308137<like,0>3394245602834314645<like,0>5662213458193308137"
+                "<like,0>-7579316172763586624",
+            "5662213458193308137<like,0>3394245602834314645",
+            "5662213458193308137<like,0>-7579316172763586624<like,0>3394245602834314645",
+            "5662213458193308137<like,0>-7579316172763586624<like,0>5662213458193308137"
+                "<like,0>3394245602834314645",
+            "5662213458193308137<like,0>3394245602834314645<like,0>5662213458193308137"
+                "<like,0>3394245602834314645"
+        };
+        ASSERT_TRUE(verifyPath(resp, expected));
+    }
+    {
+        cpp2::ExecutionResponse resp;
+        auto *fmt = "FIND ALL PATH FROM %ld TO %ld OVER like UPTO 3 STEPS";
+        auto &tim = players_["Tim Duncan"];
+        auto &al = players_["LaMarcus Aldridge"];
+        auto query = folly::stringPrintf(fmt, tim.vid(), al.vid());
+        auto code = client_->execute(query, resp);
+        ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code) << *(resp.get_error_msg());
+        std::vector<std::string> expected = {
+            "5662213458193308137<like,0>-7579316172763586624<like,0>-1782445125509592239"
+        };
+        ASSERT_TRUE(verifyPath(resp, expected));
+    }
+    {
+        cpp2::ExecutionResponse resp;
+        auto *fmt = "FIND ALL PATH FROM %ld TO %ld OVER like UPTO 3 STEPS";
+        auto &tiago = players_["Tiago Splitter"];
+        auto &al = players_["LaMarcus Aldridge"];
+        auto query = folly::stringPrintf(fmt, tiago.vid(), al.vid());
+        auto code = client_->execute(query, resp);
+        ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code) << *(resp.get_error_msg());
+        std::vector<std::string> expected = {
+            "-8160811731890648949<like,0>5662213458193308137<like,0>-7579316172763586624"
+                "<like,0>-1782445125509592239"
+        };
+        ASSERT_TRUE(verifyPath(resp, expected));
+    }
+}
+
+TEST_F(FindPathTest, multiEdgesShortest) {
+    {
+        cpp2::ExecutionResponse resp;
+        auto *fmt = "FIND SHORTEST PATH FROM %ld TO %ld OVER like,serve UPTO 5 STEPS";
+        auto &tim = players_["Tim Duncan"];
+        auto &tony = players_["Tony Parker"];
+        auto query = folly::stringPrintf(fmt, tim.vid(), tony.vid());
+        auto code = client_->execute(query, resp);
+        ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code) << *(resp.get_error_msg());
+        std::vector<std::string> expected = {
+            "5662213458193308137<like,0>-7579316172763586624",
+        };
+        ASSERT_TRUE(verifyPath(resp, expected));
+    }
+    {
+        cpp2::ExecutionResponse resp;
+        auto *fmt = "FIND SHORTEST PATH FROM %ld TO %ld,%ld OVER like,serve UPTO 5 STEPS";
+        auto &tim = players_["Tim Duncan"];
+        auto &tony = players_["Tony Parker"];
+        auto query = folly::stringPrintf(fmt, tim.vid(), tony.vid(), teams_["Spurs"].vid());
+        auto code = client_->execute(query, resp);
+        ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code) << *(resp.get_error_msg());
+        std::vector<std::string> expected = {
+            "5662213458193308137<like,0>-7579316172763586624",
+            "5662213458193308137<serve,0>7193291116733635180",
+        };
+        ASSERT_TRUE(verifyPath(resp, expected));
+    }
+    {
+        cpp2::ExecutionResponse resp;
+        auto *fmt = "FIND SHORTEST PATH FROM %ld TO %ld,%ld,%ld,%ld "
+                    "OVER like,serve UPTO 5 STEPS";
+        auto &tim = players_["Tim Duncan"];
+        auto &tony = players_["Tony Parker"];
+        auto &manu = players_["Manu Ginobili"];
+        auto &al = players_["LaMarcus Aldridge"];
+        auto query = folly::stringPrintf(fmt,
+                tim.vid(),
+                tony.vid(), manu.vid(), al.vid(), teams_["Spurs"].vid());
+        auto code = client_->execute(query, resp);
+        ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code) << *(resp.get_error_msg());
+        std::vector<std::string> expected = {
+            "5662213458193308137<like,0>3394245602834314645",
+            "5662213458193308137<like,0>-7579316172763586624",
+            "5662213458193308137<like,0>-7579316172763586624<like,0>-1782445125509592239",
+            "5662213458193308137<serve,0>7193291116733635180",
+        };
+        ASSERT_TRUE(verifyPath(resp, expected));
+    }
+    {
+        cpp2::ExecutionResponse resp;
+        auto *fmt = "FIND SHORTEST PATH FROM %ld TO %ld,%ld OVER like,serve UPTO 5 STEPS";
+        auto &tiago = players_["Tiago Splitter"];
+        auto &al = players_["LaMarcus Aldridge"];
+        auto query = folly::stringPrintf(fmt, tiago.vid(), al.vid(), teams_["Spurs"].vid());
+        auto code = client_->execute(query, resp);
+        ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code) << *(resp.get_error_msg());
+        std::vector<std::string> expected = {
+            "-8160811731890648949<like,0>5662213458193308137<like,0>-7579316172763586624"
+                "<like,0>-1782445125509592239",
+            "-8160811731890648949<serve,0>7193291116733635180",
+        };
+        ASSERT_TRUE(verifyPath(resp, expected));
+    }
+    {
+        cpp2::ExecutionResponse resp;
+        auto *fmt = "FIND SHORTEST PATH FROM %ld TO %ld OVER * UPTO 5 STEPS";
+        auto &tim = players_["Tim Duncan"];
+        auto &tony = players_["Tony Parker"];
+        auto query = folly::stringPrintf(fmt, tim.vid(), tony.vid());
+        auto code = client_->execute(query, resp);
+        ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code) << *(resp.get_error_msg());
+        std::vector<std::string> expected = {
+            "5662213458193308137<like,0>-7579316172763586624",
+        };
+        ASSERT_TRUE(verifyPath(resp, expected));
+    }
+    {
+        cpp2::ExecutionResponse resp;
+        auto *fmt = "FIND SHORTEST PATH FROM %ld TO %ld,%ld OVER * UPTO 5 STEPS";
+        auto &tim = players_["Tim Duncan"];
+        auto &tony = players_["Tony Parker"];
+        auto query = folly::stringPrintf(fmt, tim.vid(), tony.vid(), teams_["Spurs"].vid());
+        auto code = client_->execute(query, resp);
+        ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code) << *(resp.get_error_msg());
+        std::vector<std::string> expected = {
+            "5662213458193308137<like,0>-7579316172763586624",
+            "5662213458193308137<serve,0>7193291116733635180",
+        };
+        ASSERT_TRUE(verifyPath(resp, expected));
+    }
+    {
+        cpp2::ExecutionResponse resp;
+        auto *fmt = "FIND SHORTEST PATH FROM %ld TO %ld,%ld,%ld,%ld "
+                    "OVER * UPTO 5 STEPS";
+        auto &tim = players_["Tim Duncan"];
+        auto &tony = players_["Tony Parker"];
+        auto &manu = players_["Manu Ginobili"];
+        auto &al = players_["LaMarcus Aldridge"];
+        auto query = folly::stringPrintf(fmt,
+                tim.vid(),
+                tony.vid(), manu.vid(), al.vid(), teams_["Spurs"].vid());
+        auto code = client_->execute(query, resp);
+        ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code) << *(resp.get_error_msg());
+        std::vector<std::string> expected = {
+            "5662213458193308137<like,0>3394245602834314645",
+            "5662213458193308137<like,0>-7579316172763586624",
+            "5662213458193308137<like,0>-7579316172763586624<like,0>-1782445125509592239",
+            "5662213458193308137<serve,0>7193291116733635180",
+        };
+        ASSERT_TRUE(verifyPath(resp, expected));
+    }
+    {
+        cpp2::ExecutionResponse resp;
+        auto *fmt = "FIND SHORTEST PATH FROM %ld TO %ld,%ld OVER * UPTO 5 STEPS";
+        auto &tiago = players_["Tiago Splitter"];
+        auto &al = players_["LaMarcus Aldridge"];
+        auto query = folly::stringPrintf(fmt, tiago.vid(), al.vid(), teams_["Spurs"].vid());
+        auto code = client_->execute(query, resp);
+        ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code) << *(resp.get_error_msg());
+        std::vector<std::string> expected = {
+            "-8160811731890648949<like,0>5662213458193308137<like,0>-7579316172763586624"
+                "<like,0>-1782445125509592239",
+            "-8160811731890648949<serve,0>7193291116733635180",
+        };
+        ASSERT_TRUE(verifyPath(resp, expected));
+    }
+}
+
+TEST_F(FindPathTest, multiEdgesAll) {
+    /*
+     * TODO: There might exist loops when find all path,
+     * we should provide users with an option on whether or not a loop is required.
+     */
+    {
+        cpp2::ExecutionResponse resp;
+        auto *fmt = "FIND ALL PATH FROM %ld TO %ld,%ld OVER like,serve UPTO 3 STEPS";
+        auto &tim = players_["Tim Duncan"];
+        auto &tony = players_["Tony Parker"];
+        auto query = folly::stringPrintf(fmt, tim.vid(), tony.vid(), teams_["Spurs"].vid());
+        auto code = client_->execute(query, resp);
+        ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code) << *(resp.get_error_msg());
+        std::vector<std::string> expected = {
+            "5662213458193308137<like,0>-7579316172763586624",
+            "5662213458193308137<like,0>-7579316172763586624<like,0>-1782445125509592239"
+                "<like,0>-7579316172763586624",
+            "5662213458193308137<like,0>-7579316172763586624<like,0>5662213458193308137"
+                "<like,0>-7579316172763586624",
+            "5662213458193308137<like,0>3394245602834314645<like,0>5662213458193308137"
+                "<like,0>-7579316172763586624",
+            "5662213458193308137<serve,0>7193291116733635180",
+            "5662213458193308137<like,0>-7579316172763586624<serve,0>7193291116733635180",
+            "5662213458193308137<like,0>3394245602834314645<serve,0>7193291116733635180",
+            "5662213458193308137<like,0>-7579316172763586624<like,0>-1782445125509592239"
+                "<serve,0>7193291116733635180",
+            "5662213458193308137<like,0>-7579316172763586624<like,0>3394245602834314645"
+                "<serve,0>7193291116733635180",
+            "5662213458193308137<like,0>-7579316172763586624<like,0>5662213458193308137"
+                "<serve,0>7193291116733635180",
+            "5662213458193308137<like,0>3394245602834314645<like,0>5662213458193308137"
+                "<serve,0>7193291116733635180",
+        };
+        ASSERT_TRUE(verifyPath(resp, expected));
+    }
+    {
+        cpp2::ExecutionResponse resp;
+        auto *fmt = "FIND ALL PATH FROM %ld TO %ld,%ld OVER * UPTO 3 STEPS";
+        auto &tim = players_["Tim Duncan"];
+        auto &tony = players_["Tony Parker"];
+        auto query = folly::stringPrintf(fmt, tim.vid(), tony.vid(), teams_["Spurs"].vid());
+        auto code = client_->execute(query, resp);
+        ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code) << *(resp.get_error_msg());
+        std::vector<std::string> expected = {
+            "5662213458193308137<like,0>-7579316172763586624",
+            "5662213458193308137<like,0>-7579316172763586624<like,0>-1782445125509592239"
+                "<like,0>-7579316172763586624",
+            "5662213458193308137<like,0>-7579316172763586624<like,0>5662213458193308137"
+                "<like,0>-7579316172763586624",
+            "5662213458193308137<like,0>3394245602834314645<like,0>5662213458193308137"
+                "<like,0>-7579316172763586624",
+            "5662213458193308137<serve,0>7193291116733635180",
+            "5662213458193308137<like,0>-7579316172763586624<serve,0>7193291116733635180",
+            "5662213458193308137<like,0>3394245602834314645<serve,0>7193291116733635180",
+            "5662213458193308137<like,0>-7579316172763586624<like,0>-1782445125509592239"
+                "<serve,0>7193291116733635180",
+            "5662213458193308137<like,0>-7579316172763586624<like,0>3394245602834314645"
+                "<serve,0>7193291116733635180",
+            "5662213458193308137<like,0>-7579316172763586624<like,0>5662213458193308137"
+                "<serve,0>7193291116733635180",
+            "5662213458193308137<like,0>3394245602834314645<like,0>5662213458193308137"
+                "<serve,0>7193291116733635180",
+        };
+        ASSERT_TRUE(verifyPath(resp, expected));
+    }
+}
+}  // namespace graph
+}  // namespace nebula
diff --git a/src/executor/test/TestBase.h b/src/executor/test/TestBase.h
index 379e31a46dc363f2c3e08bafa74ab0126515a7f2..171a0efd91175b51d816aca492c4602a53f9987d 100644
--- a/src/executor/test/TestBase.h
+++ b/src/executor/test/TestBase.h
@@ -214,7 +214,7 @@ protected:
             std::sort(rows.begin(), rows.end());
             std::sort(expected.begin(), expected.end());
         }
-        for (auto i = 0u; i < rows.size(); i++) {
+        for (decltype(rows.size()) i = 0; i < rows.size(); ++i) {
             if (rows[i] != expected[i]) {
                 return TestError() << rows[i] << " vs. " << expected[i];
             }
diff --git a/src/executor/test/TraverseTestBase.h b/src/executor/test/TraverseTestBase.h
index 021dcae532a7ceb15e7be9925d2a8e7f3866d882..f1d2cb80285e179d5410875fdc0500fd1fc744e5 100644
--- a/src/executor/test/TraverseTestBase.h
+++ b/src/executor/test/TraverseTestBase.h
@@ -44,6 +44,59 @@ protected:
         client_.reset();
     }
 
+    AssertionResult verifyPath(const cpp2::ExecutionResponse &resp,
+                               std::vector<std::string> &expected) {
+        if (resp.get_error_code() != cpp2::ErrorCode::SUCCEEDED) {
+            auto *errmsg = resp.get_error_msg();
+            return TestError() << "Query failed with `"
+                               << static_cast<int32_t>(resp.get_error_code())
+                               << (errmsg == nullptr ? "'" : "': " + *errmsg);
+        }
+
+        if (resp.get_rows() == nullptr && expected.empty()) {
+            return TestOK();
+        }
+
+        auto rows = buildPathString(*resp.get_rows());
+
+        if (expected.size() != rows.size()) {
+            return TestError() << "Rows' count not match: "
+                               << rows.size() << " vs. " << expected.size();
+        }
+
+        std::sort(rows.begin(), rows.end());
+        std::sort(expected.begin(), expected.end());
+
+        for (decltype(rows.size()) i = 0; i < rows.size(); ++i) {
+            if (rows[i] != expected[i]) {
+                return TestError() << rows[i] << " vs. " << expected[i];
+            }
+        }
+        return TestOK();
+    }
+
+    static std::vector<std::string> buildPathString(std::vector<cpp2::RowValue> rows) {
+        std::vector<std::string> paths;
+        for (auto &row : rows) {
+            auto &pathValue = row.get_columns().back().get_path();
+            auto &cols = pathValue.get_entry_list();
+            std::string pathStr;
+            auto iter = cols.begin();
+            while (iter < (cols.end() - 1)) {
+                pathStr += folly::stringPrintf("%ld<%s,%ld>",
+                                iter->get_vertex().get_id(),
+                                (iter + 1)->get_edge().get_type().c_str(),
+                                (iter + 1)->get_edge().get_ranking());
+                iter = iter + 2;
+            }
+            pathStr += folly::to<std::string>(iter->get_vertex().get_id());
+            paths.emplace_back(std::move(pathStr));
+        }
+
+        return paths;
+    }
+
+
     static AssertionResult prepareSchema();
 
     static AssertionResult prepareData();
diff --git a/src/parser/Clauses.cpp b/src/parser/Clauses.cpp
index 278cc44b27f337dec63dab2e95cfb97c93c12733..bcc8de2a539feaf5d7006e1bac9dcc2c84863b62 100644
--- a/src/parser/Clauses.cpp
+++ b/src/parser/Clauses.cpp
@@ -48,6 +48,51 @@ std::string VertexIDList::toString() const {
     return buf;
 }
 
+Status VerticesClause::prepare(Clause::Vertices &vertices) const {
+    Status status;
+    if (isRef()) {
+        if (ref_->isInputExpression()) {
+            auto *iexpr = static_cast<InputPropertyExpression*>(ref_.get());
+            vertices.colname_ = iexpr->prop();
+        } else if (ref_->isVariableExpression()) {
+            auto *vexpr = static_cast<VariablePropertyExpression*>(ref_.get());
+            vertices.varname_ = vexpr->alias();
+            vertices.colname_ = vexpr->prop();
+        } else {
+            //  should never come to here.
+            //  only support input and variable yet.
+            status = Status::Error("Unknown kind of expression.");
+        }
+    } else {
+        auto uniqID = std::make_unique<std::unordered_set<VertexID>>();
+        auto vidList = vidList_->vidList();
+        vertices.vids_.reserve(vidList.size());
+        for (auto *expr : vidList) {
+            status = expr->prepare();
+            if (!status.ok()) {
+                break;
+            }
+            auto value = expr->eval();
+            if (!value.ok()) {
+                status = value.status();
+                break;
+            }
+            auto v = value.value();
+            if (!Expression::isInt(v)) {
+                status = Status::Error("Vertex ID should be of type integer");
+                break;
+            }
+
+            auto valInt = Expression::asInt(v);
+            auto result = uniqID->emplace(valInt);
+            if (result.second) {
+                vertices.vids_.emplace_back(valInt);
+            }
+        }
+    }
+    return status;
+}
+
 std::string FromClause::toString() const {
     std::string buf;
     buf.reserve(256);
@@ -60,6 +105,22 @@ std::string FromClause::toString() const {
     return buf;
 }
 
+std::string ToClause::toString() const {
+    std::string buf;
+    buf.reserve(256);
+    buf += "TO ";
+    if (isRef()) {
+        buf += ref_->toString();
+    } else {
+        buf += vidList_->toString();
+    }
+    return buf;
+}
+
+Status OverClause::prepare(Over &over) const {
+    over.edges_ = edges();
+    return Status::OK();
+}
 
 std::string OverEdge::toString() const {
     std::string buf;
@@ -95,6 +156,11 @@ std::string OverClause::toString() const {
     return buf;
 }
 
+Status WhereClause::prepare(Where &where) const {
+    where.filter_ = filter_.get();
+    return Status::OK();
+}
+
 std::string WhereClause::toString() const {
     std::string buf;
     buf.reserve(256);
@@ -131,5 +197,4 @@ std::string YieldClause::toString() const {
     buf += yieldColumns_->toString();
     return buf;
 }
-
 }   // namespace nebula
diff --git a/src/parser/Clauses.h b/src/parser/Clauses.h
index 1f8f8b6de258d665a1484f3b6e5a90eb86710d7d..96b9e53141e10e026b8b53d490798e70ec3d795e 100644
--- a/src/parser/Clauses.h
+++ b/src/parser/Clauses.h
@@ -11,11 +11,60 @@
 
 namespace nebula {
 
-class StepClause final {
+class OverEdge;
+class Clause {
+public:
+    struct Vertices {
+        std::string             *colname_{nullptr};
+        std::string             *varname_{nullptr};
+        std::vector<VertexID>    vids_;
+    };
+
+    struct Over {
+        std::vector<OverEdge*>  edges_{nullptr};
+        std::vector<EdgeType>   edgeTypes_;
+        std::vector<EdgeType>   oppositeTypes_;
+    };
+
+    struct Step {
+        uint32_t steps_{0};
+        bool     upto_{false};
+    };
+
+    struct Where {
+        Expression *filter_{nullptr};
+    };
+
+protected:
+    enum Kind : uint8_t {
+        kUnknown = 0,
+
+        kStepClause,
+        kOverClause,
+        kFromClause,
+        kToClause,
+        kWhereClause,
+        kYieldClause,
+
+        kMax,
+    };
+
+protected:
+    Kind    kind_{kUnknown};
+};
+
+class StepClause final : public Clause {
 public:
     explicit StepClause(uint64_t steps = 1, bool isUpto = false) {
         steps_ = steps;
         isUpto_ = isUpto;
+        kind_ = Kind::kStepClause;
+    }
+
+    Status prepare(Step &step) const {
+        step.steps_ = steps_;
+        step.upto_ = isUpto_;
+        return Status::OK();
     }
 
     uint32_t steps() const {
@@ -51,7 +100,6 @@ private:
 };
 
 
-
 class VertexIDList final {
 public:
     void add(Expression *expr) {
@@ -74,13 +122,13 @@ private:
 };
 
 
-class FromClause final {
+class VerticesClause : public Clause {
 public:
-    explicit FromClause(VertexIDList *vidList) {
+    explicit VerticesClause(VertexIDList *vidList) {
         vidList_.reset(vidList);
     }
 
-    explicit FromClause(Expression *ref) {
+    explicit VerticesClause(Expression *ref) {
         ref_.reset(ref);
     }
 
@@ -96,13 +144,39 @@ public:
         return ref_.get();
     }
 
-    std::string toString() const;
+    Status prepare(Vertices &vertices) const;
 
-private:
+protected:
     std::unique_ptr<VertexIDList>               vidList_;
     std::unique_ptr<Expression>                 ref_;
 };
 
+class FromClause final : public VerticesClause {
+public:
+    explicit FromClause(VertexIDList *vidList) : VerticesClause(vidList) {
+        kind_ = kFromClause;
+    }
+
+    explicit FromClause(Expression *ref) : VerticesClause(ref) {
+        kind_ = kFromClause;
+    }
+
+    std::string toString() const;
+};
+
+class ToClause final : public VerticesClause {
+public:
+    explicit ToClause(VertexIDList *vidList) : VerticesClause(vidList) {
+        kind_ = kToClause;
+    }
+
+    explicit ToClause(Expression *ref) : VerticesClause(ref) {
+        kind_ = kToClause;
+    }
+
+    std::string toString() const;
+};
+
 class OverEdge final {
 public:
     explicit OverEdge(std::string *edge, std::string *alias = nullptr, bool isReversely = false) {
@@ -146,19 +220,24 @@ private:
     std::vector<std::unique_ptr<OverEdge>> edges_;
 };
 
-class OverClause final {
+class OverClause final : public Clause {
 public:
-    explicit OverClause(OverEdges *edges) { overEdges_.reset(edges); }
+    explicit OverClause(OverEdges *edges) {
+        kind_ = kOverClause;
+        overEdges_.reset(edges);
+    }
 
     std::vector<OverEdge *> edges() const { return overEdges_->edges(); }
 
+    Status prepare(Over &over) const;
+
     std::string toString() const;
 
 private:
     std::unique_ptr<OverEdges> overEdges_;
 };
 
-class WhereClause final {
+class WhereClause final : public Clause {
 public:
     explicit WhereClause(Expression *filter) {
         filter_.reset(filter);
@@ -168,6 +247,8 @@ public:
         return filter_.get();
     }
 
+    Status prepare(Where &where) const;
+
     std::string toString() const;
 
 private:
@@ -239,7 +320,5 @@ private:
     std::unique_ptr<YieldColumns>               yieldColumns_;
     bool                                        distinct_;
 };
-
-}  // namespace nebula
-
+}   // namespace nebula
 #endif  // PARSER_CLAUSES_H_
diff --git a/src/parser/Sentence.h b/src/parser/Sentence.h
index a69eecc9917e9783e3230bd48f072c63240c5bb0..9a6bd30cf0b8938f89d5e53cb4147e02fa24aae8 100644
--- a/src/parser/Sentence.h
+++ b/src/parser/Sentence.h
@@ -59,6 +59,7 @@ public:
         kFetchVertices,
         kFetchEdges,
         kBalance,
+        kFindPath,
     };
 
     Kind kind() const {
diff --git a/src/parser/TraverseSentences.cpp b/src/parser/TraverseSentences.cpp
index ab2e388d21058294ace0200a28aacdf6e57e1bfd..60be945dee4c293d1d74d3bf5ce437d1adcc9ade 100644
--- a/src/parser/TraverseSentences.cpp
+++ b/src/parser/TraverseSentences.cpp
@@ -261,4 +261,37 @@ std::string FetchEdgesSentence::toString() const {
     }
     return buf;
 }
+
+std::string FindPathSentence::toString() const {
+    std::string buf;
+    buf.reserve(256);
+    buf += "FIND ";
+    if (isShortest_) {
+        buf += "SHORTEST PATH ";
+    } else {
+        buf += "ALL PATH ";
+    }
+
+    if (from_ != nullptr) {
+        buf += from_->toString();
+        buf += " ";
+    }
+    if (to_ != nullptr) {
+        buf += to_->toString();
+        buf += " ";
+    }
+    if (over_ != nullptr) {
+        buf += over_->toString();
+        buf += " ";
+    }
+    if (step_ != nullptr) {
+        buf += step_->toString();
+        buf += " ";
+    }
+    if (where_ != nullptr) {
+        buf += where_->toString();
+        buf += " ";
+    }
+    return buf;
+}
 }   // namespace nebula
diff --git a/src/parser/TraverseSentences.h b/src/parser/TraverseSentences.h
index abf242d92828d0628eeb32113f50a2904c3c5caa..e00684da3d3b94980a70c8d321e92ee3f7d1f433 100644
--- a/src/parser/TraverseSentences.h
+++ b/src/parser/TraverseSentences.h
@@ -483,5 +483,67 @@ private:
     std::unique_ptr<EdgeKeyRef>     keyRef_;
     std::unique_ptr<YieldClause>    yieldClause_;
 };
+
+class FindPathSentence final : public Sentence {
+public:
+    explicit FindPathSentence(bool isShortest) {
+        kind_ = Kind::kFindPath;
+        isShortest_ = isShortest;
+    }
+
+    void setFrom(FromClause *clause) {
+        from_.reset(clause);
+    }
+
+    void setTo(ToClause *clause) {
+        to_.reset(clause);
+    }
+
+    void setOver(OverClause *clause) {
+        over_.reset(clause);
+    }
+
+    void setStep(StepClause *clause) {
+        step_.reset(clause);
+    }
+
+    void setWhere(WhereClause *clause) {
+        where_.reset(clause);
+    }
+
+    FromClause* from() const {
+        return from_.get();
+    }
+
+    ToClause* to() const {
+        return to_.get();
+    }
+
+    OverClause* over() const {
+        return over_.get();
+    }
+
+    StepClause* step() const {
+        return step_.get();
+    }
+
+    WhereClause* where() const {
+        return where_.get();
+    }
+
+    bool isShortest() const {
+        return isShortest_;
+    }
+
+    std::string toString() const override;
+
+private:
+    bool                            isShortest_;
+    std::unique_ptr<FromClause>     from_;
+    std::unique_ptr<ToClause>       to_;
+    std::unique_ptr<OverClause>     over_;
+    std::unique_ptr<StepClause>     step_;
+    std::unique_ptr<WhereClause>    where_;
+};
 }   // namespace nebula
 #endif  // PARSER_TRAVERSESENTENCES_H_
diff --git a/src/parser/parser.yy b/src/parser/parser.yy
index ddd79b974f7a6a72217d2c0ef376bea0bd87d27b..e73838e01656acdb6e32d2c1fad5ad4ccd1e7032 100644
--- a/src/parser/parser.yy
+++ b/src/parser/parser.yy
@@ -42,7 +42,9 @@ class GraphScanner;
     nebula::ColumnNameList                 *colsnamelist;
     nebula::ColumnType                      type;
     nebula::StepClause                     *step_clause;
+    nebula::StepClause                     *find_path_upto_clause;
     nebula::FromClause                     *from_clause;
+    nebula::ToClause                       *to_clause;
     nebula::VertexIDList                   *vid_list;
     nebula::OverEdge                       *over_edge;
     nebula::OverEdges                      *over_edges;
@@ -108,6 +110,7 @@ class GraphScanner;
 %token KW_FETCH KW_PROP KW_UPDATE KW_UPSERT KW_WHEN
 %token KW_DISTINCT KW_ALL KW_OF
 %token KW_BALANCE KW_LEADER KW_DATA
+%token KW_SHORTEST KW_PATH
 
 /* symbols */
 %token L_PAREN R_PAREN L_BRACKET R_BRACKET L_BRACE R_BRACE COMMA
@@ -175,6 +178,8 @@ class GraphScanner;
 %type <edge_key> edge_key
 %type <edge_keys> edge_keys
 %type <edge_key_ref> edge_key_ref
+%type <to_clause> to_clause
+%type <find_path_upto_clause> find_path_upto_clause
 
 %type <intval> port unary_integer rank
 
@@ -187,7 +192,7 @@ class GraphScanner;
 %type <role_type_clause> role_type_clause
 %type <acl_item_clause> acl_item_clause
 
-%type <sentence> go_sentence match_sentence use_sentence find_sentence
+%type <sentence> go_sentence match_sentence use_sentence find_sentence find_path_sentence
 %type <sentence> order_by_sentence
 %type <sentence> fetch_vertices_sentence fetch_edges_sentence
 %type <sentence> create_tag_sentence create_edge_sentence
@@ -716,9 +721,9 @@ edge_key
     : vid R_ARROW vid AT rank {
         $$ = new EdgeKey($1, $3, $5);
     }
-	| vid R_ARROW vid {
+    | vid R_ARROW vid {
         $$ = new EdgeKey($1, $3, 0);
-	}
+    }
     ;
 
 edge_keys
@@ -767,6 +772,43 @@ fetch_sentence
     | fetch_edges_sentence { $$ = $1; }
     ;
 
+find_path_sentence
+    : KW_FIND KW_ALL KW_PATH from_clause to_clause over_clause find_path_upto_clause
+    /* where_clause */ {
+        auto *s = new FindPathSentence(false);
+        s->setFrom($4);
+        s->setTo($5);
+        s->setOver($6);
+        s->setStep($7);
+        /* s->setWhere($8); */
+        $$ = s;
+    }
+    | KW_FIND KW_SHORTEST KW_PATH from_clause to_clause over_clause find_path_upto_clause
+    /* where_clause */ {
+        auto *s = new FindPathSentence(true);
+        s->setFrom($4);
+        s->setTo($5);
+        s->setOver($6);
+        s->setStep($7);
+        /* s->setWhere($8); */
+        $$ = s;
+    }
+    ;
+
+find_path_upto_clause
+    : %empty { $$ = new StepClause(5, true); }
+    | KW_UPTO INTEGER KW_STEPS { $$ = new StepClause($2, true); }
+    ;
+
+to_clause
+    : KW_TO vid_list {
+        $$ = new ToClause($2);
+    }
+    | KW_TO vid_ref_expression {
+        $$ = new ToClause($2);
+    }
+    ;
+
 use_sentence
     : KW_USE name_label { $$ = new UseSentence($2); }
     ;
@@ -976,13 +1018,14 @@ drop_edge_sentence
     ;
 
 traverse_sentence
-    : go_sentence { $$ = $1; }
+    : L_PAREN piped_sentence R_PAREN { $$ = $2; }
+    | L_PAREN set_sentence R_PAREN { $$ = $2; }
+    | go_sentence { $$ = $1; }
     | match_sentence { $$ = $1; }
     | find_sentence { $$ = $1; }
     | order_by_sentence { $$ = $1; }
     | fetch_sentence { $$ = $1; }
-    | L_PAREN piped_sentence R_PAREN { $$ = $2; }
-    | L_PAREN set_sentence R_PAREN { $$ = $2; }
+    | find_path_sentence { $$ = $1; }
     ;
 
 set_sentence
diff --git a/src/parser/scanner.lex b/src/parser/scanner.lex
index b6755139462e3b88d578c8323306e12c88dc5987..4c5165882586534e7d343060f8436c322a0e85b5 100644
--- a/src/parser/scanner.lex
+++ b/src/parser/scanner.lex
@@ -122,6 +122,8 @@ LEADER                      ([Ll][Ee][Aa][Dd][Ee][Rr])
 UUID                        ([Uu][Uu][Ii][Dd])
 OF                          ([Oo][Ff])
 DATA                        ([Dd][Aa][Tt][Aa])
+SHORTEST                    ([Ss][Hh][Oo][Rr][Tt][Ee][Ss][Tt])
+PATH                        ([Pp][Aa][Tt][Hh])
 
 LABEL                       ([a-zA-Z][_a-zA-Z0-9]*)
 DEC                         ([0-9])
@@ -233,6 +235,8 @@ IP_OCTET                    ([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])
 {LEADER}                    { return TokenType::KW_LEADER; }
 {UUID}                      { return TokenType::KW_UUID; }
 {DATA}                      { return TokenType::KW_DATA; }
+{SHORTEST}                  { return TokenType::KW_SHORTEST; }
+{PATH}                      { return TokenType::KW_PATH; }
 
 "."                         { return TokenType::DOT; }
 ","                         { return TokenType::COMMA; }
diff --git a/src/parser/test/ParserTest.cpp b/src/parser/test/ParserTest.cpp
index eff8d674045c7140fabb1cb019bec450f626c511..13b689021df25a6991a9460c37461d1972b9fa6a 100644
--- a/src/parser/test/ParserTest.cpp
+++ b/src/parser/test/ParserTest.cpp
@@ -1368,4 +1368,12 @@ TEST(Parser, CrashByFuzzer) {
     }
 }
 
+TEST(Parser, FindPath) {
+    {
+        GQLParser parser;
+        std::string query = "FIND SHORTEST PATH FROM 1 TO 2 OVER like";
+        auto result = parser.parse(query);
+        ASSERT_TRUE(result.ok()) << result.status();
+    }
+}
 }   // namespace nebula