You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by ke...@apache.org on 2021/09/06 07:21:47 UTC

[skywalking] branch master updated: Rebuilt ElasticSearch client on top of their REST API (#7634)

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 26b81a2  Rebuilt ElasticSearch client on top of their REST API (#7634)
26b81a2 is described below

commit 26b81a2e5991ecdd2bde0f7071264cb80bb235cf
Author: kezhenxu94 <ke...@apache.org>
AuthorDate: Mon Sep 6 15:21:35 2021 +0800

    Rebuilt ElasticSearch client on top of their REST API (#7634)
---
 .github/actions/e2e-test/action.yml                |   2 +-
 .github/workflows/ci-it.yaml                       |   3 +
 .github/workflows/docker-ci.yaml                   |   4 +-
 .github/workflows/e2e.istio.yaml                   |   5 +-
 .github/workflows/e2e.jdk-versions.yaml            |   1 -
 .github/workflows/publish-docker.yaml              |   5 -
 CHANGES.md                                         |   2 +
 Makefile                                           |   9 +-
 apm-dist-es7/pom.xml                               | 105 ----
 apm-dist-es7/src/main/assembly/binary-es7.xml      | 103 ----
 apm-dist/src/main/assembly/binary.xml              |   2 +-
 dist-material/release-docs/LICENSE                 |  28 +-
 docker/.env                                        |   3 +-
 docker/README.md                                   |  24 +-
 docker/docker-compose.yml                          |   4 +-
 docker/oap/log4j2.xml                              |   6 +-
 docs/en/concepts-and-designs/event.md              |   2 +-
 docs/en/concepts-and-designs/mal.md                |   2 +-
 docs/en/guides/backend-profile.md                  |   4 +-
 docs/en/setup/backend/backend-fetcher.md           |   2 +-
 docs/en/setup/backend/backend-meter.md             |   2 +-
 docs/en/setup/backend/backend-receivers.md         |   5 +-
 docs/en/setup/backend/backend-storage.md           |  30 +-
 docs/en/setup/backend/backend-telemetry.md         |   2 +-
 docs/en/setup/backend/configuration-vocabulary.md  |  29 +-
 docs/en/setup/backend/spring-sleuth-setup.md       |   2 +-
 docs/en/setup/envoy/als_setting.md                 |   2 +-
 docs/en/setup/envoy/examples/metrics/log4j2.xml    |   1 -
 oap-server-bom-es7/pom.xml                         |  56 --
 oap-server-bom/pom.xml                             |  60 +-
 .../skywalking/oap/meter/analyzer/Analyzer.java    |  13 +-
 .../oap/meter/analyzer/MetricConvert.java          |   4 +-
 .../exporter/provider/grpc/GRPCExporterTest.java   |   4 +-
 oap-server/pom.xml                                 |   2 -
 oap-server/server-alarm-plugin/pom.xml             |   6 +-
 .../src/test/resources/log4j2-test.xml             |   1 -
 oap-server/server-bootstrap/pom.xml                | 295 ---------
 ...TClusterModuleConsulProviderFunctionalTest.java |   2 +-
 .../cluster-etcd-plugin/pom.xml                    |  22 -
 .../cluster/plugin/etcd/EtcdCoordinator.java       |   2 +-
 .../plugin/etcd/ITClusterEtcdPluginTest.java       |  23 +-
 .../ITClusterModuleEtcdProviderFunctionalTest.java |  50 +-
 .../cluster-nacos-plugin/pom.xml                   | 110 +---
 ...ITClusterModuleNacosProviderFunctionalTest.java |  50 +-
 .../configuration-apollo/pom.xml                   |   5 +
 .../configuration-etcd/pom.xml                     |  22 -
 .../etcd/ITEtcdConfigurationTest.java              |  63 +-
 .../core/cluster/ServiceRegisterException.java     |   4 +
 .../core/profile/analyze/ProfileAnalyzer.java      |   2 +-
 .../server/core/query/AggregationQueryService.java |   4 +-
 .../core/storage/ttl/DataTTLKeeperTimer.java       |   7 +
 .../storage/type/StorageDataComplexObject.java     |  18 +
 .../oap/server/core/remote/JettyServerTest.java    | 145 ++---
 oap-server/server-library/library-client/pom.xml   | 114 +---
 .../client/elasticsearch/ElasticSearchClient.java  | 669 ++++++---------------
 ...InsertRequest.java => IndexRequestWrapper.java} |  25 +-
 .../client/elasticsearch/UpdateRequestWrapper.java |  37 ++
 .../client/jdbc/hikaricp/JDBCHikariCPClient.java   |   5 +-
 .../elasticsearch/bulk/ITElasticSearch.java        | 255 ++++++++
 .../elasticsearch/ITElasticSearchClient.java       | 307 ----------
 .../library-elasticsearch-client}/pom.xml          |  37 +-
 .../library/elasticsearch/ElasticSearch.java       | 182 ++++++
 .../elasticsearch/ElasticSearchBuilder.java        | 189 ++++++
 .../elasticsearch/ElasticSearchVersion.java        |  99 +++
 .../library/elasticsearch/bulk/BulkProcessor.java  | 152 +++++
 .../elasticsearch/bulk/BulkProcessorBuilder.java   |  57 ++
 .../library/elasticsearch/client/AliasClient.java  |  71 +++
 .../elasticsearch/client/DocumentClient.java       | 157 +++++
 .../library/elasticsearch/client/IndexClient.java  | 167 +++++
 .../library/elasticsearch/client/SearchClient.java |  75 +++
 .../elasticsearch/client/TemplateClient.java       | 149 +++++
 .../elasticsearch/requests/IndexRequest.java}      |  19 +-
 .../elasticsearch/requests/UpdateRequest.java}     |  19 +-
 .../requests/factory/AliasFactory.java}            |  11 +-
 .../requests/factory/BulkFactory.java}             |  17 +-
 .../elasticsearch/requests/factory/Codec.java}     |  22 +-
 .../requests/factory/DocumentFactory.java          |  57 ++
 .../requests/factory/IndexFactory.java             |  51 ++
 .../requests/factory/RequestFactory.java           |  62 ++
 .../requests/factory/SearchFactory.java}           |  25 +-
 .../requests/factory/TemplateFactory.java}         |  34 +-
 .../factory/common/CommonAliasFactory.java         |  41 ++
 .../requests/factory/common/CommonBulkFactory.java |  52 ++
 .../factory/common/CommonSearchFactory.java        |  63 ++
 .../requests/factory/v6/V6DocumentFactory.java     | 174 ++++++
 .../requests/factory/v6/V6IndexFactory.java        | 105 ++++
 .../requests/factory/v6/V6RequestFactory.java      |  52 ++
 .../requests/factory/v6/V6TemplateFactory.java     |  87 +++
 .../requests/factory/v6/codec/V6Codec.java         |  81 +++
 .../factory/v6/codec/V6IndexRequestSerializer.java |  48 ++
 .../factory/v6/codec/V6MappingsDeserializer.java   |  54 ++
 .../factory/v6/codec/V6MappingsSerializer.java     |  43 ++
 .../v6/codec/V6UpdateRequestSerializer.java        |  54 ++
 .../requests/factory/v7/V78RequestFactory.java     |  52 ++
 .../requests/factory/v7/V78TemplateFactory.java    |  86 +++
 .../requests/factory/v7/V7DocumentFactory.java     | 168 ++++++
 .../requests/factory/v7/V7IndexFactory.java        | 103 ++++
 .../requests/factory/v7/V7RequestFactory.java      |  52 ++
 .../requests/factory/v7/V7TemplateFactory.java     |  81 +++
 .../requests/factory/v7/codec/V7Codec.java         |  77 +++
 .../factory/v7/codec/V7IndexRequestSerializer.java |  47 ++
 .../factory/v7/codec/V7MappingsDeserializer.java   |  49 ++
 .../v7/codec/V7UpdateRequestSerializer.java        |  53 ++
 .../elasticsearch/requests/search/BoolQuery.java   |  81 +++
 .../requests/search/BoolQueryBuilder.java          | 133 ++++
 .../elasticsearch/requests/search/IdsQuery.java    |  55 ++
 .../requests/search/MatchPhaseQuery.java           |  53 ++
 .../elasticsearch/requests/search/MatchQuery.java  |  49 ++
 .../elasticsearch/requests/search/Query.java       |  86 +++
 .../requests/search/QueryBuilder.java}             |  11 +-
 .../elasticsearch/requests/search/RangeQuery.java  |  77 +++
 .../requests/search/RangeQueryBuilder.java         |  70 +++
 .../elasticsearch/requests/search/Search.java}     |  37 +-
 .../requests/search/SearchBuilder.java             | 114 ++++
 .../elasticsearch/requests/search/Sort.java}       |  30 +-
 .../elasticsearch/requests/search/Sorts.java       |  70 +++
 .../elasticsearch/requests/search/TermQuery.java   |  55 ++
 .../elasticsearch/requests/search/TermsQuery.java  |  56 ++
 .../requests/search/aggregation/Aggregation.java   |  50 ++
 .../search/aggregation/AggregationBuilder.java}    |   7 +-
 .../search/aggregation/AvgAggregation.java         |  57 ++
 .../search/aggregation/AvgAggregationBuilder.java} |  29 +-
 .../requests/search/aggregation/BucketOrder.java}  |  21 +-
 .../search/aggregation/MaxAggregation.java         |  57 ++
 .../search/aggregation/MaxAggregationBuilder.java} |  27 +-
 .../search/aggregation/MinAggregation.java         |  57 ++
 .../search/aggregation/MinAggregationBuilder.java} |  29 +-
 .../search/aggregation/SumAggregation.java         |  57 ++
 .../search/aggregation/SumAggregationBuilder.java} |  29 +-
 .../search/aggregation/TermsAggregation.java       |  65 ++
 .../aggregation/TermsAggregationBuilder.java       |  87 +++
 .../library/elasticsearch/response/Document.java}  |  19 +-
 .../library/elasticsearch/response/Documents.java} |  21 +-
 .../library/elasticsearch/response/Index.java}     |  14 +-
 .../elasticsearch/response/IndexTemplate.java}     |  18 +-
 .../elasticsearch/response/IndexTemplates.java}    |  33 +-
 .../library/elasticsearch/response/Mappings.java}  |  35 +-
 .../library/elasticsearch/response/NodeInfo.java}  |  15 +-
 .../elasticsearch/response/search/SearchHit.java}  |  40 +-
 .../elasticsearch/response/search/SearchHits.java  |  66 ++
 .../response/search/SearchResponse.java}           |  15 +-
 .../library/elasticsearch/ITElasticSearchTest.java | 191 ++++++
 oap-server/server-library/pom.xml                  |   3 +-
 .../oap/query/graphql/resolver/MetricQuery.java    |   9 +-
 .../pom.xml                                        |   2 +-
 .../discovery/AgentConfigurationsReader.java       |  12 +-
 .../discovery/AgentConfigurationsWatcher.java      |  12 +-
 .../receiver/envoy/EnvoyMetricReceiverConfig.java  |   2 +-
 .../zipkin-receiver-plugin/pom.xml                 |   4 +-
 oap-server/server-starter-es7/pom.xml              |  92 ---
 .../src/main/assembly/assembly.xml                 |  33 -
 oap-server/server-starter/pom.xml                  | 257 +++++++-
 .../oap/server/starter/OAPServerBootstrap.java     |   0
 .../oap/server/starter/OAPServerStartUp.java       |   3 -
 .../starter/config/ApplicationConfigLoader.java    |   0
 .../config/ConfigFileNotFoundException.java        |   0
 .../oap/server/starter/config/ConfigLoader.java    |   0
 .../src/main/resources/alarm-settings.yml          |   0
 .../src/main/resources/application.yml             |  35 +-
 .../src/main/resources/component-libraries.yml     |   0
 .../src/main/resources/endpoint-name-grouping.yml  |   0
 .../envoy-metrics-rules/envoy-svc-relation.yaml    |   0
 .../main/resources/envoy-metrics-rules/envoy.yaml  |   0
 .../main/resources/fetcher-prom-rules/self.yaml    |   0
 .../src/main/resources/gateways.yml                |   0
 .../src/main/resources/lal/default.yaml            |   0
 .../src/main/resources/lal/envoy-als.yaml          |   0
 .../main/resources/log-mal-rules/placeholder.yaml  |   0
 .../src/main/resources/log4j2.xml                  |   6 +-
 .../main/resources/metadata-service-mapping.yaml   |   0
 .../meter-analyzer-config/spring-sleuth.yaml       |   0
 .../src/main/resources/oal/browser.oal             |   0
 .../src/main/resources/oal/core.oal                |   0
 .../src/main/resources/oal/disable.oal             |   0
 .../src/main/resources/oal/dotnet-agent.oal        |   0
 .../src/main/resources/oal/event.oal               |   0
 .../src/main/resources/oal/java-agent.oal          |   0
 .../src/main/resources/oal/tcp.oal                 |   0
 .../serviceA/productAPI-v1.yaml                    |   0
 .../otel-oc-rules/istio-controlplane.yaml          |   0
 .../main/resources/otel-oc-rules/k8s-cluster.yaml  |   0
 .../src/main/resources/otel-oc-rules/k8s-node.yaml |   0
 .../main/resources/otel-oc-rules/k8s-service.yaml  |   0
 .../src/main/resources/otel-oc-rules/oap.yaml      |   0
 .../src/main/resources/otel-oc-rules/vm.yaml       |   0
 .../src/main/resources/service-apdex-threshold.yml |   0
 .../resources/trace-sampling-policy-settings.yml   |   0
 .../resources/ui-initialized-templates/apm.yml     |   0
 .../resources/ui-initialized-templates/browser.yml |   0
 .../ui-initialized-templates/database.yml          |   0
 .../resources/ui-initialized-templates/event.yml   |   0
 .../ui-initialized-templates/istio-dp.yml          |   0
 .../resources/ui-initialized-templates/istio.yml   |   0
 .../resources/ui-initialized-templates/k8s.yml     |   0
 .../self-observability.yml                         |   0
 .../ui-initialized-templates/spring-sleuth.yml     |   0
 .../topology-endpoint-relation.yml                 |   0
 .../ui-initialized-templates/topology-endpoint.yml |   0
 .../ui-initialized-templates/topology-instance.yml |   0
 .../topology-service-instance-relation.yml         |   0
 .../topology-service-relation.yml                  |   0
 .../ui-initialized-templates/topology-service.yml  |   0
 .../main/resources/ui-initialized-templates/vm.yml |   0
 .../src/main/resources/zabbix-rules/agent.yaml     |   0
 .../oap/server/starter/ComponentLibrariesTest.java |   0
 .../oap/server/starter/UITemplateCheckerTest.java  |   0
 .../config/ApplicationConfigLoaderTestCase.java    |   0
 oap-server/server-storage-plugin/pom.xml           |   5 +-
 .../storage-elasticsearch-plugin/pom.xml           |   8 +-
 .../StorageModuleElasticsearchConfig.java          |   6 +
 .../StorageModuleElasticsearchProvider.java        |  37 +-
 .../elasticsearch/base/BatchProcessEsDAO.java      |  12 +-
 .../storage/plugin/elasticsearch/base/EsDAO.java   |  22 -
 .../elasticsearch/base/HistoryDeleteEsDAO.java     |  26 +-
 .../plugin/elasticsearch/base/IndexStructures.java | 109 ++--
 .../plugin/elasticsearch/base/ManagementEsDAO.java |  16 +-
 .../plugin/elasticsearch/base/MetricsEsDAO.java    |  37 +-
 .../plugin/elasticsearch/base/NoneStreamEsDAO.java |   7 +-
 .../plugin/elasticsearch/base/RecordEsDAO.java     |   6 +-
 .../elasticsearch/base/StorageEsInstaller.java     |  72 +--
 ...Maker.java => TimeRangeIndexNameGenerator.java} |  10 +-
 .../plugin/elasticsearch/base/TimeSeriesUtils.java |   4 +-
 .../cache/NetworkAddressAliasEsDAO.java            |  25 +-
 .../elasticsearch/query/AggregationQueryEsDAO.java | 109 ++--
 .../elasticsearch/query/AlarmQueryEsDAO.java       |  51 +-
 .../elasticsearch/query/BrowserLogQueryEsDAO.java  |  55 +-
 .../elasticsearch/query/ESEventQueryDAO.java       | 139 ++---
 .../plugin/elasticsearch/query/LogQueryEsDAO.java  |  90 +--
 .../elasticsearch/query/MetadataQueryEsDAO.java    | 165 +++--
 .../elasticsearch/query/MetricsQueryEsDAO.java     | 206 ++++---
 .../elasticsearch/query/ProfileTaskLogEsDAO.java   |  40 +-
 .../elasticsearch/query/ProfileTaskQueryEsDAO.java |  91 +--
 .../query/ProfileThreadSnapshotQueryEsDAO.java     | 239 ++++----
 .../elasticsearch/query/TopNRecordsQueryEsDAO.java |  50 +-
 .../elasticsearch/query/TopologyQueryEsDAO.java    | 339 ++++++-----
 .../elasticsearch/query/TraceQueryEsDAO.java       | 113 ++--
 .../query/UITemplateManagementEsDAO.java           |  92 +--
 ...alking.oap.server.library.module.ModuleProvider |   2 +-
 .../elasticsearch/base/IndexStructuresTest.java    |  87 ++-
 .../elasticsearch/base/TimeSeriesUtilsTest.java    |   2 +-
 .../storage-elasticsearch7-plugin/pom.xml          |  53 --
 .../StorageModuleElasticsearch7Provider.java       | 240 --------
 .../elasticsearch7/base/StorageEs7Installer.java   |  40 --
 .../client/ElasticSearch7Client.java               | 429 -------------
 .../plugin/elasticsearch7/dao/MetricsEs7DAO.java   |  31 -
 .../plugin/elasticsearch7/dao/StorageEs7DAO.java   |  63 --
 .../query/AggregationQueryEs7DAO.java              | 120 ----
 .../elasticsearch7/query/AlarmQueryEs7DAO.java     | 107 ----
 .../query/BrowserLogQueryEs7DAO.java               |  96 ---
 .../elasticsearch7/query/ES7EventQueryDAO.java     |  62 --
 .../elasticsearch7/query/LogQueryEs7DAO.java       | 165 -----
 .../elasticsearch7/query/MetricsQueryEs7DAO.java   | 102 ----
 .../query/ProfileThreadSnapshotQueryEs7DAO.java    |  48 --
 .../elasticsearch7/query/TraceQueryEs7DAO.java     | 151 -----
 ...alking.oap.server.library.module.ModuleProvider |  19 -
 .../storage/plugin/influxdb/query/AlarmQuery.java  |   5 +-
 .../storage/plugin/influxdb/query/LogQuery.java    |   2 +-
 .../influxdb/query/ProfileThreadSnapshotQuery.java |   2 +-
 .../storage/plugin/influxdb/query/TraceQuery.java  |   2 +-
 .../plugin/jdbc/h2/dao/H2AlarmQueryDAO.java        |  21 +-
 .../storage/plugin/jdbc/h2/dao/H2LogQueryDAO.java  |   3 +-
 .../h2/dao/H2ProfileThreadSnapshotQueryDAO.java    |   6 +-
 .../plugin/jdbc/h2/dao/H2TraceQueryDAO.java        |   5 +-
 .../pom.xml                                        |  15 +-
 .../server/storage/plugin/zipkin/ZipkinSpan.java   |   0
 .../storage/plugin/zipkin/ZipkinSpanRecord.java    |   0
 .../plugin/zipkin/ZipkinSpanRecordDispatcher.java  |   0
 .../ZipkinStorageModuleElasticsearchProvider.java  |   9 +-
 .../elasticsearch/ZipkinTraceQueryEsDAO.java}      |  83 ++-
 ...alking.oap.server.library.module.ModuleProvider |   0
 oap-server/server-tools/pom.xml                    |   4 +-
 oap-server/server-tools/profile-exporter/pom.xml   |   7 +-
 .../tool-profile-snapshot-bootstrap/pom.xml        |   5 +-
 .../pom.xml                                        |   9 +-
 .../profile/exporter/ProfileSnapshotExporter.java  |   0
 .../tool-profile-snapshot-exporter/pom.xml         |   5 +-
 pom.xml                                            |   4 -
 test/e2e/e2e-common/pom.xml                        |   2 +-
 test/e2e/e2e-data/pom.xml                          |   2 +-
 .../e2e-test/docker/alarm/docker-compose.es7.yml   |   4 +-
 test/e2e/e2e-test/docker/base-compose.yml          |  23 -
 .../e2e-test/docker/browser/docker-compose.es7.yml |   6 +-
 .../docker/cluster/docker-compose.zk.es7.yml       |   8 +-
 .../e2e-test/docker/event/docker-compose.es7.0.yml |   4 +-
 .../e2e-test/docker/log/docker-compose.es7.14.yml  |   4 +-
 .../e2e/e2e-test/docker/log/docker-compose.es7.yml |   4 +-
 test/e2e/e2e-test/docker/nodejs/Dockerfile.nodejs  |   2 +-
 .../e2e-test/docker/profile/docker-compose.es7.yml |   4 +-
 .../docker/storage/docker-compose.es7.0.yml        |   4 +-
 .../docker/storage/docker-compose.es7.10.yml       |   4 +-
 .../docker/storage/docker-compose.es7.14.yml       |   4 +-
 .../docker/storage/docker-compose.opensearch.yml   |   4 +-
 .../e2e/e2e-test/docker/ttl/docker-compose.es7.yml |   4 +-
 test/e2e/e2e-test/pom.xml                          |   2 +-
 .../apache/skywalking/e2e/ttl/StorageTTLE2E.java   |   8 +-
 test/e2e/pom.xml                                   |  32 +-
 tools/dependencies/check-LICENSE.sh                |  15 +-
 .../known-oap-backend-dependencies-es7.txt         | 174 ------
 .../known-oap-backend-dependencies.txt             |  53 +-
 tools/profile-exporter/application.yml             |  20 -
 300 files changed, 7481 insertions(+), 5622 deletions(-)

diff --git a/.github/actions/e2e-test/action.yml b/.github/actions/e2e-test/action.yml
index f1ab13e..45aaa5c 100644
--- a/.github/actions/e2e-test/action.yml
+++ b/.github/actions/e2e-test/action.yml
@@ -41,7 +41,7 @@ runs:
       run: |
         echo "::group::Build Docker Image"
         # Retry one more time due to frequent "maven connection reset"
-        (make docker || make docker) && (ES_VERSION=es7 TAG=latest-es7 make docker.oap || ES_VERSION=es7 TAG=latest-es7 make docker.oap)
+        make docker || make docker
         echo "::endgroup::"
     - name: Run E2E Test
       shell: bash
diff --git a/.github/workflows/ci-it.yaml b/.github/workflows/ci-it.yaml
index 0b360b0..0dbb4ed 100644
--- a/.github/workflows/ci-it.yaml
+++ b/.github/workflows/ci-it.yaml
@@ -25,6 +25,9 @@ concurrency:
   group: ci-it-${{ github.event.pull_request.number || github.ref }}
   cancel-in-progress: true
 
+env:
+  MAVEN_OPTS: -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false -Dmaven.wagon.httpconnectionManager.ttlSeconds=120
+
 jobs:
   check-license-header:
     if: (github.event_name == 'schedule' && github.repository == 'apache/skywalking') || (github.event_name != 'schedule')
diff --git a/.github/workflows/docker-ci.yaml b/.github/workflows/docker-ci.yaml
index a408b03..202a01e0 100644
--- a/.github/workflows/docker-ci.yaml
+++ b/.github/workflows/docker-ci.yaml
@@ -59,7 +59,7 @@ jobs:
           restore-keys: |
             ${{ runner.os }}-maven-
       - name: Build docker image
-        run: export ES_VERSION=${{ matrix.es }} && export TAG=${{ matrix.es }} && (make docker || make docker)
+        run: make docker || make docker
       - name: Bootstrap cluster
         run: |
           case ${{ matrix.es }} in
@@ -67,7 +67,7 @@ jobs:
           es7) export ES_TAG=7.5.0 ;;
           esac
 
-          export TAG=${{ matrix.es }}
+          export TAG=latest
           cd ${DOCKER_DIR} && docker-compose up -d
       - name: Check port available
         run: |
diff --git a/.github/workflows/e2e.istio.yaml b/.github/workflows/e2e.istio.yaml
index 004db04..74f9fcf 100644
--- a/.github/workflows/e2e.istio.yaml
+++ b/.github/workflows/e2e.istio.yaml
@@ -26,7 +26,6 @@ on:
 
 env:
   SKIP_TEST: true
-  ES_VERSION: es7
   TAG: ${{ github.sha }}
   SCRIPTS_DIR: test/e2e-mesh/e2e-istio/scripts
   SW_OAP_BASE_IMAGE: openjdk:11-jdk
@@ -103,7 +102,7 @@ jobs:
                --set ui.image.tag=$TAG \
                --set oap.image.tag=$TAG \
                --set oap.image.repository=skywalking/oap \
-               --set oap.storageType=elasticsearch7
+               --set oap.storageType=elasticsearch
           kubectl -n istio-system get pods
 
           sleep 3
@@ -233,7 +232,7 @@ jobs:
                --set ui.image.tag=$TAG \
                --set oap.image.tag=$TAG \
                --set oap.image.repository=skywalking/oap \
-               --set oap.storageType=elasticsearch7
+               --set oap.storageType=elasticsearch
           kubectl -n istio-system get pods
 
           sleep 3
diff --git a/.github/workflows/e2e.jdk-versions.yaml b/.github/workflows/e2e.jdk-versions.yaml
index edfbec1..f865195 100644
--- a/.github/workflows/e2e.jdk-versions.yaml
+++ b/.github/workflows/e2e.jdk-versions.yaml
@@ -42,7 +42,6 @@ jobs:
       - uses: actions/checkout@v2
         with:
           submodules: true
-          depth: 0
       - name: Set Skip Env Var
         uses: ./.github/actions/skip
       - name: Set Up Java
diff --git a/.github/workflows/publish-docker.yaml b/.github/workflows/publish-docker.yaml
index 28fd290..d8bb783 100644
--- a/.github/workflows/publish-docker.yaml
+++ b/.github/workflows/publish-docker.yaml
@@ -33,12 +33,7 @@ jobs:
       contents: read
       packages: write
     timeout-minutes: 90
-    strategy:
-      fail-fast: true
-      matrix:
-        es: [es6, es7]
     env:
-      ES_VERSION: ${{ matrix.es }}
       TAG: ${{ github.sha }}-${{ matrix.es }}
     steps:
       - uses: actions/checkout@v2
diff --git a/CHANGES.md b/CHANGES.md
index f63527f..a3df298 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -55,6 +55,8 @@ Release Notes.
 * Add yaml file suffix limit when reading ui templates.
 * Support consul grouped dynamic configurations.
 * Fix `H2MetadataQueryDAO.searchService` doesn't support auto grouping.
+* Rebuilt ElasticSearch client on top of their REST API.
+* Fix ElasticSearch storage plugin doesn't work when hot reloading from `secretsManagementFile`.
 
 #### UI
 
diff --git a/Makefile b/Makefile
index c870429..51f13ca 100644
--- a/Makefile
+++ b/Makefile
@@ -21,6 +21,7 @@ export SW_ROOT := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
 export SW_OUT:=${SW_ROOT}/dist
 
 SKIP_TEST?=false
+DIST_NAME := apache-skywalking-apm-bin
 
 init:
 	cd $(SW_ROOT) && git submodule update --init --recursive
@@ -42,8 +43,6 @@ HUB?=skywalking
 
 TAG?=latest
 
-ES_VERSION?=es6
-
 .SECONDEXPANSION: #allow $@ to be used in dependency list
 
 .PHONY: docker docker.all docker.oap
@@ -54,12 +53,6 @@ DOCKER_TARGETS:=docker.oap docker.ui
 
 docker.all: $(DOCKER_TARGETS)
 
-ifeq ($(ES_VERSION),es7)
-  DIST_NAME := apache-skywalking-apm-bin-es7
-else
-  DIST_NAME := apache-skywalking-apm-bin
-endif
-
 ifneq ($(SW_OAP_BASE_IMAGE),)
   BUILD_ARGS := $(BUILD_ARGS) --build-arg BASE_IMAGE=$(SW_OAP_BASE_IMAGE)
 endif
diff --git a/apm-dist-es7/pom.xml b/apm-dist-es7/pom.xml
deleted file mode 100644
index 955b1c5..0000000
--- a/apm-dist-es7/pom.xml
+++ /dev/null
@@ -1,105 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-  ~ 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.
-  ~
-  -->
-
-<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">
-    <parent>
-        <artifactId>apm</artifactId>
-        <groupId>org.apache.skywalking</groupId>
-        <version>8.8.0-SNAPSHOT</version>
-    </parent>
-    <modelVersion>4.0.0</modelVersion>
-
-    <artifactId>apache-skywalking-apm-es7</artifactId>
-    <packaging>pom</packaging>
-
-    <profiles>
-        <profile>
-            <id>backend</id>
-            <activation>
-                <activeByDefault>true</activeByDefault>
-            </activation>
-            <dependencies>
-                <dependency>
-                    <groupId>org.apache.skywalking</groupId>
-                    <artifactId>server-starter-es7</artifactId>
-                    <version>${project.version}</version>
-                </dependency>
-            </dependencies>
-        </profile>
-        <profile>
-            <id>ui</id>
-            <activation>
-                <activeByDefault>true</activeByDefault>
-            </activation>
-            <dependencies>
-                <dependency>
-                    <groupId>org.apache.skywalking</groupId>
-                    <artifactId>apm-webapp</artifactId>
-                    <version>${project.version}</version>
-                </dependency>
-            </dependencies>
-        </profile>
-    </profiles>
-
-    <build>
-        <plugins>
-            <plugin>
-                <artifactId>maven-assembly-plugin</artifactId>
-                <executions>
-                    <execution>
-                        <id>dist-es7</id>
-                        <phase>package</phase>
-                        <goals>
-                            <goal>single</goal>
-                        </goals>
-                        <configuration>
-                            <finalName>apache-skywalking-apm-bin-es7</finalName>
-                            <descriptors>
-                                <descriptor>${project.basedir}/src/main/assembly/binary-es7.xml</descriptor>
-                            </descriptors>
-                        </configuration>
-                    </execution>
-                </executions>
-                <configuration>
-                    <attach>true</attach>
-                    <tarLongFileMode>posix</tarLongFileMode>
-                    <runOnlyAtExecutionRoot>false</runOnlyAtExecutionRoot>
-                    <appendAssemblyId>false</appendAssemblyId>
-                </configuration>
-            </plugin>
-            <plugin>
-                <artifactId>maven-antrun-plugin</artifactId>
-                <executions>
-                    <execution>
-                        <id>dist-es7</id>
-                        <phase>package</phase>
-                        <goals>
-                            <goal>run</goal>
-                        </goals>
-                        <configuration>
-                            <target>
-                                <copy file="${project.build.directory}/apache-skywalking-apm-bin-es7.tar.gz" tofile="${project.basedir}/../dist/apache-skywalking-apm-bin-es7.tar.gz" overwrite="true" />
-                            </target>
-                        </configuration>
-                    </execution>
-                </executions>
-            </plugin>
-        </plugins>
-    </build>
-</project>
diff --git a/apm-dist-es7/src/main/assembly/binary-es7.xml b/apm-dist-es7/src/main/assembly/binary-es7.xml
deleted file mode 100644
index d19cb1d..0000000
--- a/apm-dist-es7/src/main/assembly/binary-es7.xml
+++ /dev/null
@@ -1,103 +0,0 @@
-<!--
-  ~ Licensed to the Apache Software Foundation (ASF) under one or more
-  ~ contributor license agreements.  See the NOTICE file distributed with
-  ~ this work for additional information regarding copyright ownership.
-  ~ The ASF licenses this file to You under the Apache License, Version 2.0
-  ~ (the "License"); you may not use this file except in compliance with
-  ~ the License.  You may obtain a copy of the License at
-  ~
-  ~     http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  ~
-  -->
-
-<assembly
-        xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2"
-        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-        xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
-    <id>dist</id>
-    <formats>
-        <format>tar.gz</format>
-    </formats>
-    <fileSets>
-        <fileSet>
-            <directory>${project.basedir}/../dist-material/bin</directory>
-            <outputDirectory>bin</outputDirectory>
-            <includes>
-                <include>*.sh</include>
-                <include>*.bat</include>
-            </includes>
-            <fileMode>0755</fileMode>
-        </fileSet>
-        <fileSet>
-            <directory>${project.basedir}/../dist-material</directory>
-            <outputDirectory>config</outputDirectory>
-            <includes>
-                <include>log4j2.xml</include>
-                <include>alarm-settings.yml</include>
-            </includes>
-        </fileSet>
-        <fileSet>
-            <directory>${project.basedir}/../dist-material</directory>
-            <outputDirectory/>
-            <includes>
-                <include>config-examples/*</include>
-            </includes>
-        </fileSet>
-        <fileSet>
-            <directory>${project.basedir}/../oap-server/server-bootstrap/src/main/resources</directory>
-            <includes>
-                <include>application.yml</include>
-                <include>component-libraries.yml</include>
-                <include>gateways.yml</include>
-                <include>service-apdex-threshold.yml</include>
-                <include>endpoint-name-grouping.yml</include>
-                <include>metadata-service-mapping.yaml</include>
-                <include>trace-sampling-policy-settings.yml</include>
-                <include>oal/*.oal</include>
-                <include>fetcher-prom-rules/*.yaml</include>
-                <include>envoy-metrics-rules/*.yaml</include>
-                <include>meter-analyzer-config/*.yaml</include>
-                <include>zabbix-rules/*.yaml</include>
-                <include>openapi-definitions/*</include>
-                <include>otel-oc-rules/*</include>
-                <include>ui-initialized-templates/*</include>
-                <include>lal/*</include>
-                <include>log-mal-rules/*</include>
-            </includes>
-            <outputDirectory>config</outputDirectory>
-        </fileSet>
-        <fileSet>
-            <directory>${project.basedir}/../oap-server/server-starter-es7/target/skywalking-oap-assembly/skywalking-oap/libs</directory>
-            <outputDirectory>oap-libs</outputDirectory>
-        </fileSet>
-
-        <!-- Profile exporter tools -->
-        <fileSet>
-            <directory>${project.basedir}/../tools/profile-exporter</directory>
-            <outputDirectory>tools/profile-exporter</outputDirectory>
-        </fileSet>
-
-        <fileSet>
-            <directory>${project.basedir}/../dist-material/release-docs</directory>
-            <outputDirectory/>
-        </fileSet>
-    </fileSets>
-    <files>
-        <file>
-            <source>${project.basedir}/../apm-webapp/target/skywalking-webapp.jar</source>
-            <outputDirectory>webapp</outputDirectory>
-            <fileMode>0644</fileMode>
-        </file>
-        <file>
-            <source>${project.basedir}/../apm-webapp/src/main/assembly/webapp.yml</source>
-            <outputDirectory>webapp</outputDirectory>
-            <fileMode>0644</fileMode>
-        </file>
-    </files>
-</assembly>
diff --git a/apm-dist/src/main/assembly/binary.xml b/apm-dist/src/main/assembly/binary.xml
index bc047ab..a66a6c9 100644
--- a/apm-dist/src/main/assembly/binary.xml
+++ b/apm-dist/src/main/assembly/binary.xml
@@ -50,7 +50,7 @@
             </includes>
         </fileSet>
         <fileSet>
-            <directory>${project.basedir}/../oap-server/server-bootstrap/src/main/resources</directory>
+            <directory>${project.basedir}/../oap-server/server-starter/src/main/resources</directory>
             <includes>
                 <include>application.yml</include>
                 <include>component-libraries.yml</include>
diff --git a/dist-material/release-docs/LICENSE b/dist-material/release-docs/LICENSE
index 97c7696..5332bf5 100755
--- a/dist-material/release-docs/LICENSE
+++ b/dist-material/release-docs/LICENSE
@@ -233,19 +233,6 @@ The text of each license is the standard Apache 2.0 license.
     Google: proto-google-common-protos 1.17.0: https://github.com/googleapis/googleapis , Apache 2.0
     Google: jsr305 3.0.2: http://central.maven.org/maven2/com/google/code/findbugs/jsr305/3.0.0/jsr305-3.0.0.pom , Apache 2.0
     Google: flatbuffers-java 1.12.0: https://github.com/google/flatbuffers/ , Apache 2.0
-    Elasticsearch BV (Elasticsearch) 6.3.2: https://www.elastic.co/products/elasticsearch , Apache 2.0
-    Elasticsearch BV (Elasticsearch) 7.5.0: https://www.elastic.co/products/elasticsearch , Apache 2.0
-    aggs-matrix-stats-client 6.3.2, 7.5.0: https://github.com/elastic/elasticsearch/tree/master/modules/aggs-matrix-stats Apache 2.0
-    lang-mustache-client 7.5.0: https://github.com/elastic/elasticsearch/tree/master/modules/lang-mustache Apache 2.0
-    mapper-extras-client 7.5.0: https://github.com/elastic/elasticsearch/tree/master/modules/mapper-extras Apache 2.0
-    parent-join-client 6.3.2, 7.5.0: https://github.com/elastic/elasticsearch/tree/master/modules/parent-join , Apache 2.0
-    rank-eval-client 6.3.2, 7.5.0: https://github.com/elastic/elasticsearch/tree/master/modules/rank-eval , Apache 2.0
-    reindex-client 5.5.0: https://github.com/elastic/elasticsearch/tree/master/modules/reindex , Apache 2.0
-    percolator-client 5.5.0: https://github.com/elastic/elasticsearch/tree/master/modules/percolator , Apache 2.0
-    rest 5.5.0: https://github.com/elastic/elasticsearch/tree/master/client/rest , Apache 2.0
-    transport 5.5.0: https://github.com/elastic/elasticsearch/tree/master/client/transport , Apache 2.0
-    securesm 1.1: https://github.com/elastic/securesm/blob/master/pom.xml , Apache 2.0
-    LMAX Ltd.(disruptor) 3.3.6: https://github.com/LMAX-Exchange/disruptor , Apache 2.0
     Eclipse (Jetty) 9.4.40.v20210413: https://www.eclipse.org/jetty/ , Apache 2.0 and Eclipse Public License 1.0
     SnakeYAML 1.28: http://www.snakeyaml.org , Apache 2.0
     Joda-Time 2.10.5: http://www.joda.org/joda-time/ , Apache 2.0
@@ -256,15 +243,13 @@ The text of each license is the standard Apache 2.0 license.
     Spring Cloud Load Balancer 3.0.3: https://spring.io/projects/spring-cloud, Apache-2.0
     Spring Cloud Config 1.4.1: https://github.com/spring-cloud/spring-cloud-config, Apache-2.0
     Spring Cloud Netflix Zuul 1.3.0: https://github.com/spring-cloud/spring-cloud-netflix, Apache 2.0
-    Apache: commons-logging 1.1.3: https://github.com/apache/commons-logging, Apache 2.0
+    Apache: commons-logging 1.2: https://github.com/apache/commons-logging, Apache 2.0
     Apache: commons-codec 1.10: https://github.com/apache/commons-codec, Apache 2.0
     Apache: commons-dbcp 1.4: https://github.com/apache/commons-dbcp, Apache 2.0
     Apache: commons-pool 2.4.2: https://github.com/apache/commons-pool, Apache 2.0
     Apache: commons-lang 3.6: https://github.com/apache/commons-lang, Apache 2.0
     Apache: commons-text 1.8: https://github.com/apache/commons-text, Apache 2.0
     Apache: commons-beanutils 1.9.4: https://github.com/apache/commons-beanutils, Apache 2.0
-    Apache: lucene 7.3.1, 8.3.0: https://github.com/apache/lucene-solr/tree/master/lucene, Apache 2.0
-    Apache: httpasyncclient 4.1.2, 4.1.4: https://github.com/apache/httpasyncclient/tree/4.1.2, Apache 2.0
     Apache: log4j2 2.14.1: https://github.com/apache/logging-log4j2, Apache 2.0
     Apache: zookeeper 3.5.7: https://github.com/apache/zookeeper, Apache 2.0
     Apache: commons-collections 3.2.2: https://github.com/apache/commons-collections, Apache 2.0
@@ -277,20 +262,16 @@ The text of each license is the standard Apache 2.0 license.
     annotations 13.0: http://www.jetbrains.org, Apache 2.0
     compiler 0.9.6: https://github.com/spullara/mustache.java, Apache 2.0
     error_prone_annotations 2.3.2: https://github.com/google/error-prone, Apache 2.0
-    hppc 0.7.1, 0.8.1: https://github.com/carrotsearch/hppc, Apache 2.0
     instrumentation-api 0.4.3: https://github.com/google/instrumentation-java, Apache 2.0
     jackson-annotations 2.8.0: https://github.com/FasterXML/jackson-annotations, Apache 2.0
     jackson-core 2.12.2: https://github.com/FasterXML/jackson-core, Apache 2.0
     jackson-databind 2.12.2: https://github.com/FasterXML/jackson-databind, Apache 2.0
-    jackson-dataformat 2.8.10, 2.8.11: https://github.com/FasterXML/jackson-dataformats-binary, Apache 2.0
     jackson-datatype-jdk8 2.9.5: https://github.com/FasterXML/jackson-modules-java8/tree/jackson-modules-java8-2.8.8, Apache 2.0
     jackson-module-kotlin 2.8.8: http://kotlinlang.org, Apache 2.0
     jackson-module-afterburner 2.12.2: https://github.com/FasterXML/jackson-modules-base, Apache 2.0
     java-dataloader 2.0.1: https://github.com/graphql-java/java-dataloader, Apache 2.0
-    jna 4.4.0: https://github.com/java-native-access/jna, Apache 2.0
     kotlin-reflect 1.1.1: http://kotlinlang.org, Apache 2.0
     kotlin-stdlib 1.1.60: http://kotlinlang.org, Apache 2.0
-    t-digest 3.2: https://github.com/tdunning/t-digest, Apache 2.0
     archaius-core 0.7.4: https://github.com/Netflix/archaius, Apache 2.0
     classmate 1.3.1: https://github.com/FasterXML/java-classmate, Apache 2.0
     hibernate-validator 5.3.6.Final: https://github.com/hibernate/hibernate-validator, Apache 2.0
@@ -343,6 +324,9 @@ The text of each license is the standard Apache 2.0 license.
     simpleclient_httpserver 0.11 from prometheus https://github.com/prometheus/client_java Apache 2.0
     jetcd 0.5.3, https://github.com/etcd-io/jetcd, Apache 2.0
     failasfe 2.3.4, https://github.com/jhalterman/failsafe, Apache 2.0
+    Armeria 1.11.0, http://github.com/line/armeria, Apache 2.0
+    Brotli4j 1.6.0, https://github.com/hyperxpro/Brotli4j, Apache 2.0
+    micrometer 1.7.3, https://github.com/micrometer-metrics/micrometer, Apache 2.0
 
 ========================================================================
 MIT licenses
@@ -355,7 +339,6 @@ The text of each license is also included at licenses/LICENSE-[project].txt.
     Jedis 2.9.0: https://github.com/xetorthio/jedis , MIT
     GraphQL java 8.0: https://github.com/graphql-java/graphql-java , MIT
     GraphQL Java Tools 5.2.3: https://github.com/graphql-java/graphql-java-tools , MIT
-    jopt-simple 5.0.2: https://github.com/jopt-simple/jopt-simple , MIT
     bcpkix-jdk15on 1.69: http://www.bouncycastle.org/licence.html , MIT
     bcprov-jdk15on 1.69: http://www.bouncycastle.org/licence.html , MIT
     bcprov-ext-jdk15on 1.69: http://www.bouncycastle.org/licence.html , MIT
@@ -390,6 +373,7 @@ The text of each license is also included at licenses/LICENSE-[project].txt.
     proto files from grpc-gateway, https://github.com/grpc-ecosystem/grpc-gateway/tree/master/protoc-gen-swagger/options BSD-3
     zstd-jni 1.4.3-1: https://github.com/luben/zstd-jni, BSD-3-Clause
     postgresql 42.2.18: https://jdbc.postgresql.org/about/license.html, BSD-2-Clause
+    LatencyUtils 2.0.3: https://github.com/LatencyUtils/LatencyUtils, BSD-2-Clause
 
 ========================================================================
 MPL 2.0 licenses
@@ -404,7 +388,7 @@ The text of each license is also included at licenses/LICENSE-[project].txt.
 CC0-1.0 licenses
 ========================================
 
-HdrHistogram 2.1.9:	https://github.com/HdrHistogram/HdrHistogram , CC0-1.0 and BSD 2-Clause
+HdrHistogram 2.1.12:	https://github.com/HdrHistogram/HdrHistogram , CC0-1.0 and BSD 2-Clause
 reactive-streams 1.0.2:	https://github.com/reactive-streams/reactive-streams-jvm , CC0-1.0
 
 ========================================================================
diff --git a/docker/.env b/docker/.env
index 4c19017..78afd2c 100644
--- a/docker/.env
+++ b/docker/.env
@@ -1,2 +1 @@
-TAG=es6
-ES_TAG=6.8.1
\ No newline at end of file
+ES_TAG=7.4.2
diff --git a/docker/README.md b/docker/README.md
index 00699ba..15efb19 100644
--- a/docker/README.md
+++ b/docker/README.md
@@ -29,27 +29,6 @@ The hub of docker image. The default value is `skywalking`.
 
 The tag of docker image. The default value is `latest`.
 
-### ES_VERSION
-
-The elasticsearch version this image supports. The default value is `es6`, available values are `es6` and `es7`.
-
-
-For example, if we want to build images with a hub `foo.io` and a tag `bar`, and it supports elasticsearch 7 at the same time.
-We can issue the following commands.
-
-```
-export HUB=foo.io && export TAG=bar && export ES_VERSION=es7 && make docker
-```
-
-Let's check out the result:
-```
-docker image ls | grep foo.io
-foo.io/ui                                       bar                 a14db4e1d70d        9 minutes ago       800MB
-foo.io/oap                                      bar                 2a6084450b44        9 minutes ago       862MB
-```
-
-From the output, we can find out the building tool adopts the files stored in `oap-es7`.
-
 ## Running containers with docker-compose
 
 We can start up backend cluster by docker-compose
@@ -57,4 +36,5 @@ We can start up backend cluster by docker-compose
 cd docker
 docker compose up
 ```
-`docker/.env` file contains the default `TAG` and elasticsearch tag(`ES_TAG`).
+
+[docker/.env](./.env) file contains the default elasticsearch tag (`ES_TAG`).
diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml
index a41cab8..37c0dee 100644
--- a/docker/docker-compose.yml
+++ b/docker/docker-compose.yml
@@ -29,7 +29,7 @@ services:
         soft: -1
         hard: -1
   oap:
-    image: skywalking/oap:${TAG}
+    image: skywalking/oap
     container_name: oap
     depends_on:
       - elasticsearch
@@ -51,7 +51,7 @@ services:
       retries: 3
       start_period: 40s
   ui:
-    image: skywalking/ui:${TAG}
+    image: skywalking/ui
     container_name: ui
     depends_on:
       - oap
diff --git a/docker/oap/log4j2.xml b/docker/oap/log4j2.xml
index 06242e5..2758ba6 100644
--- a/docker/oap/log4j2.xml
+++ b/docker/oap/log4j2.xml
@@ -20,18 +20,20 @@
 <Configuration status="INFO">
     <Appenders>
         <Console name="Console" target="SYSTEM_OUT">
-            <PatternLayout charset="UTF-8" pattern="%d - %c -%-4r [%t] %-5p %x - %m%n"/>
+            <PatternLayout charset="UTF-8" pattern="%d %c %L [%t] %-5p %x - %m%n"/>
         </Console>
     </Appenders>
     <Loggers>
         <logger name="org.eclipse.jetty" level="INFO"/>
         <logger name="org.apache.zookeeper" level="INFO"/>
-        <logger name="org.elasticsearch.common.network.IfConfig" level="INFO"/>
         <logger name="io.grpc.netty" level="INFO"/>
         <logger name="org.apache.skywalking.oap.meter.analyzer" level="DEBUG"/>
         <logger name="org.apache.skywalking.oap.server.receiver.istio.telemetry" level="DEBUG"/>
         <logger name="org.apache.skywalking.oap.server.fetcher.prometheus" level="DEBUG"/>
         <logger name="org.apache.skywalking.oap.server.receiver.envoy.als" level="DEBUG"/>
+        <logger name="org.apache.skywalking.oap.server.storage.plugin.elasticsearch" level="DEBUG"/>
+        <logger name="org.apache.skywalking.oap.server.core.storage.ttl" level="DEBUG"/>
+        <logger name="org.apache.skywalking.library.elasticsearch" level="DEBUG"/>
         <Root level="INFO">
             <AppenderRef ref="Console"/>
         </Root>
diff --git a/docs/en/concepts-and-designs/event.md b/docs/en/concepts-and-designs/event.md
index b73c864..52327ef 100644
--- a/docs/en/concepts-and-designs/event.md
+++ b/docs/en/concepts-and-designs/event.md
@@ -108,7 +108,7 @@ service / instance / endpoint.
 
 By default, SkyWalking also generates some metrics for events by using [OAL](oal.md). The default metrics list of event
 may change over time, you can find the complete list
-in [event.oal](../../../oap-server/server-bootstrap/src/main/resources/oal/event.oal). If you want to generate you
+in [event.oal](../../../oap-server/server-starter/src/main/resources/oal/event.oal). If you want to generate you
 custom metrics from events, please refer to [OAL](oal.md) about how to write OAL rules.
 
 ## Known Events
diff --git a/docs/en/concepts-and-designs/mal.md b/docs/en/concepts-and-designs/mal.md
index 4168489..69c7297 100644
--- a/docs/en/concepts-and-designs/mal.md
+++ b/docs/en/concepts-and-designs/mal.md
@@ -248,4 +248,4 @@ They extract level relevant labels from metric labels, then informs the meter-sy
 
 ## More Examples
 
-Please refer to [OAP Self-Observability](../../../oap-server/server-bootstrap/src/main/resources/fetcher-prom-rules/self.yaml)
+Please refer to [OAP Self-Observability](../../../oap-server/server-starter/src/main/resources/fetcher-prom-rules/self.yaml)
diff --git a/docs/en/guides/backend-profile.md b/docs/en/guides/backend-profile.md
index e582779..e6d9340 100644
--- a/docs/en/guides/backend-profile.md
+++ b/docs/en/guides/backend-profile.md
@@ -48,5 +48,5 @@ The reason for generating multiple top-level trees is that original data can be
     2. Calculate each node's duration in parallel. For each node, sort the sequences. If there are two continuous sequences, the duration should add the duration of these two seq's timestamp.
     3. Calculate each node execution in parallel. For each node, the duration of the current node should deduct the time consumed by all children.
 
-## Profile data debuggiing
-Please follow the [exporter tool](backend-profile-export.md#export-command-line-usage) to package profile data. Unzip the profile data and use [analyzer main function](../../../oap-server/server-tools/profile-exporter/tool-profile-snapshot-bootstrap/src/test/java/org/apache/skywalking/oap/server/tool/profile/exporter/ProfileExportedAnalyze.java) to run it.
+## Profile data debugging
+Please follow the [exporter tool](backend-profile-export.md#export-using-command-line) to package profile data. Unzip the profile data and use [analyzer main function](../../../oap-server/server-tools/profile-exporter/tool-profile-snapshot-bootstrap/src/test/java/org/apache/skywalking/oap/server/tool/profile/exporter/ProfileExportedAnalyze.java) to run it.
diff --git a/docs/en/setup/backend/backend-fetcher.md b/docs/en/setup/backend/backend-fetcher.md
index 717c55a..e4cdf78 100644
--- a/docs/en/setup/backend/backend-fetcher.md
+++ b/docs/en/setup/backend/backend-fetcher.md
@@ -21,7 +21,7 @@ are located at `$CLASSPATH/fetcher-prom-rules`.
 
 The file is written in YAML format, defined by the scheme described below. Brackets indicate that a parameter is optional.
 
-A full example can be found [here](../../../../oap-server/server-bootstrap/src/main/resources/fetcher-prom-rules/self.yaml)
+A full example can be found [here](../../../../oap-server/server-starter/src/main/resources/fetcher-prom-rules/self.yaml)
 
 Generic placeholders are defined as follows:
 
diff --git a/docs/en/setup/backend/backend-meter.md b/docs/en/setup/backend/backend-meter.md
index 258ee1d..6fc2fc6 100644
--- a/docs/en/setup/backend/backend-meter.md
+++ b/docs/en/setup/backend/backend-meter.md
@@ -43,7 +43,7 @@ New meter-analyzer-config files is **NOT** enabled by default, you should make m
 
 Meter-analyzer-config file is written in YAML format, defined by the scheme described below. Brackets indicate that a parameter is optional.
 
-An example can be found [here](../../../../oap-server/server-bootstrap/src/main/resources/meter-analyzer-config/spring-sleuth.yaml).
+An example can be found [here](../../../../oap-server/server-starter/src/main/resources/meter-analyzer-config/spring-sleuth.yaml).
 If you're using Spring Sleuth, see [Spring Sleuth Setup](spring-sleuth-setup.md).
 
 | Rule Name | Description | Configuration File | Data Source |
diff --git a/docs/en/setup/backend/backend-receivers.md b/docs/en/setup/backend/backend-receivers.md
index 0e59e24..3958232 100644
--- a/docs/en/setup/backend/backend-receivers.md
+++ b/docs/en/setup/backend/backend-receivers.md
@@ -170,7 +170,7 @@ for identification of the metric data.
 
 ## Zipkin receiver
 The Zipkin receiver makes the OAP server work as an alternative Zipkin server implementation. It supports Zipkin v1/v2 formats through HTTP service.
-Make sure you use this with `SW_STORAGE=zipkin-elasticsearch7` option to activate Zipkin storage implementation.
+Make sure you use this with `SW_STORAGE=zipkin-elasticsearch` option to activate Zipkin storage implementation.
 Once this receiver and storage are activated, SkyWalking's native traces would be ignored, and SkyWalking wouldn't analyze topology, metrics, and endpoint
 dependency from Zipkin's trace. 
 
@@ -189,6 +189,5 @@ receiver_zipkin:
     jettyAcceptQueueSize: ${SW_RECEIVER_ZIPKIN_QUEUE_SIZE:0}
 ```
 
-NOTE: Zipkin receiver is only provided in `apache-skywalking-apm-es7-x.y.z.tar.gz` tar.
-This requires `zipkin-elasticsearch7` storage implementation to be activated.
+NOTE: Zipkin receiver requires `zipkin-elasticsearch` storage implementation to be activated.
 Read [this](backend-storage.md#elasticsearch-7-with-zipkin-trace-extension) doc to learn about Zipkin as a storage option.
diff --git a/docs/en/setup/backend/backend-storage.md b/docs/en/setup/backend/backend-storage.md
index e750e0d..c4fd76c 100644
--- a/docs/en/setup/backend/backend-storage.md
+++ b/docs/en/setup/backend/backend-storage.md
@@ -4,7 +4,7 @@ use one of them by specifying it as the `selector` in `application.yml`:
 
 ```yaml
 storage:
-  selector: ${SW_STORAGE:elasticsearch7}
+  selector: ${SW_STORAGE:elasticsearch}
 ```
 
 Natively supported storage:
@@ -34,9 +34,8 @@ storage:
 
 ## OpenSearch
 
-OpenSearch storage shares the same configurations as ElasticSearch 7.
-In order to activate ElasticSearch 7 as storage, set storage provider to **elasticsearch7**.
-Please download the `apache-skywalking-bin-es7.tar.gz` if you want to use OpenSearch as storage.
+OpenSearch storage shares the same configurations as ElasticSearch.
+In order to activate OpenSearch as storage, set storage provider to **elasticsearch**.
 
 ## ElasticSearch
 
@@ -44,15 +43,12 @@ Please download the `apache-skywalking-bin-es7.tar.gz` if you want to use OpenSe
 License (SSPL), which is incompatible with Apache License 2.0. This license change is effective from Elasticsearch
 version 7.11. So please choose the suitable ElasticSearch version according to your usage.
 
-- In order to activate ElasticSearch 6 as storage, set storage provider to **elasticsearch**
-- In order to activate ElasticSearch 7 as storage, set storage provider to **elasticsearch7**
-
-**Required ElasticSearch 6.3.2 or higher. HTTP RestHighLevelClient is used to connect server.**
-
-- For ElasticSearch 6.3.2 ~ 7.0.0 (excluded), please download `apache-skywalking-bin.tar.gz`.
-- For ElasticSearch 7.0.0 ~ 8.0.0 (excluded), please download `apache-skywalking-bin-es7.tar.gz`.
+Since 8.8.0, SkyWalking rebuilds the ElasticSearch client on top of ElasticSearch REST API and automatically picks up
+correct request formats according to the server side version, hence you don't need to download different binaries
+and don't need to configure different storage selector for different ElasticSearch server side version anymore.
 
-For now, ElasticSearch 6 and ElasticSearch 7 share the same configurations as follows:
+For now, SkyWalking supports ElasticSearch 6.x, ElasticSearch 7.x, and OpenSearch 1.x, their configurations are as
+follows:
 
 ```yaml
 storage:
@@ -86,7 +82,7 @@ storage:
     advanced: ${SW_STORAGE_ES_ADVANCED:""}
 ```
 
-### ElasticSearch 6 With Https SSL Encrypting communications.
+### ElasticSearch With Https SSL Encrypting communications.
 
 Example: 
 
@@ -163,13 +159,13 @@ index.max_result_window: 1000000
 We strongly recommend that you read more about these configurations from ElasticSearch's official document, since they have a direct impact on the performance of ElasticSearch.
 
 
-### ElasticSearch 7 with Zipkin trace extension
-This implementation is very similar to `elasticsearch7`, except that it extends to support Zipkin span storage.
+### ElasticSearch with Zipkin trace extension
+This implementation is very similar to `elasticsearch`, except that it extends to support Zipkin span storage.
 The configurations are largely the same.
 ```yaml
 storage:
-  selector: ${SW_STORAGE:zipkin-elasticsearch7}
-  zipkin-elasticsearch7:
+  selector: ${SW_STORAGE:zipkin-elasticsearch}
+  zipkin-elasticsearch:
     nameSpace: ${SW_NAMESPACE:""}
     clusterNodes: ${SW_STORAGE_ES_CLUSTER_NODES:localhost:9200}
     protocol: ${SW_STORAGE_ES_HTTP_PROTOCOL:"http"}
diff --git a/docs/en/setup/backend/backend-telemetry.md b/docs/en/setup/backend/backend-telemetry.md
index 871f24e..e8a9658 100644
--- a/docs/en/setup/backend/backend-telemetry.md
+++ b/docs/en/setup/backend/backend-telemetry.md
@@ -120,7 +120,7 @@ Set this up following these steps:
                --set ui.image.tag=$TAG \
                --set oap.image.tag=$TAG \
                --set oap.image.repository=$HUB/skywalking-oap \
-               --set oap.storageType=elasticsearch7 \
+               --set oap.storageType=elasticsearch \
                --set oap.ports.prometheus-port=1234 \ # <<< Expose self observability metrics port
                --set oap.env.SW_TELEMETRY=prometheus \
                --set oap.env.SW_OTEL_RECEIVER=default \ # <<< Enable Otel receiver
diff --git a/docs/en/setup/backend/configuration-vocabulary.md b/docs/en/setup/backend/configuration-vocabulary.md
index 3d50453..4a71670 100644
--- a/docs/en/setup/backend/configuration-vocabulary.md
+++ b/docs/en/setup/backend/configuration-vocabulary.md
@@ -79,12 +79,13 @@ core|default|role|Option values: `Mixed/Receiver/Aggregator`. **Receiver** mode
 | - | - | password | Nacos Auth password. | SW_CLUSTER_NACOS_PASSWORD | - |
 | - | - | accessKey | Nacos Auth accessKey. | SW_CLUSTER_NACOS_ACCESSKEY | - |
 | - | - | secretKey | Nacos Auth secretKey.  | SW_CLUSTER_NACOS_SECRETKEY | - |
-| storage|elasticsearch| - | ElasticSearch 6 storage implementation. | - | - |
+| storage|elasticsearch| - | ElasticSearch (and OpenSearch) storage implementation. | - | - |
 | - | - | nameSpace | Prefix of indexes created and used by SkyWalking. | SW_NAMESPACE | - |
 | - | - | clusterNodes | ElasticSearch cluster nodes for client connection.| SW_STORAGE_ES_CLUSTER_NODES |localhost|
 | - | - | protocol | HTTP or HTTPs. | SW_STORAGE_ES_HTTP_PROTOCOL | HTTP|
 | - | - | connectTimeout | Connect timeout of ElasticSearch client (in milliseconds). | SW_STORAGE_ES_CONNECT_TIMEOUT | 500|
 | - | - | socketTimeout | Socket timeout of ElasticSearch client (in milliseconds). | SW_STORAGE_ES_SOCKET_TIMEOUT | 30000|
+| - | - | numHttpClientThread | The number of threads for the underlying HTTP client to perform socket I/O. If the value is <= 0, the number of available processors will be used. | SW_STORAGE_ES_NUM_HTTP_CLIENT_THREAD | 0 |
 | - | - | user| Username of ElasticSearch cluster. | SW_ES_USER | - |
 | - | - | password | Password of ElasticSearch cluster. | SW_ES_PASSWORD | - |
 | - | - | trustStorePath | Trust JKS file path. Only works when username and password are enabled. | SW_STORAGE_ES_SSL_JKS_PATH | - |
@@ -105,32 +106,6 @@ core|default|role|Option values: `Mixed/Receiver/Aggregator`. **Receiver** mode
 | - | - | segmentQueryMaxSize | The maximum size of trace segments per query. | SW_STORAGE_ES_QUERY_SEGMENT_SIZE | 200|
 | - | - | profileTaskQueryMaxSize | The maximum size of profile task per query. | SW_STORAGE_ES_QUERY_PROFILE_TASK_SIZE | 200|
 | - | - | advanced | All settings of ElasticSearch index creation. The value should be in JSON format. | SW_STORAGE_ES_ADVANCED | - |
-| - |elasticsearch7| - | ElasticSearch 7 storage implementation. | - | - |
-| - | - | nameSpace | Prefix of indexes created and used by SkyWalking. | SW_NAMESPACE | - |
-| - | - | clusterNodes | ElasticSearch cluster nodes for client connection.| SW_STORAGE_ES_CLUSTER_NODES |localhost|
-| - | - | protocol | HTTP or HTTPs. | SW_STORAGE_ES_HTTP_PROTOCOL | HTTP|
-| - | - | connectTimeout | Connect timeout of ElasticSearch client (in milliseconds). | SW_STORAGE_ES_CONNECT_TIMEOUT | 500|
-| - | - | socketTimeout | Socket timeout of ElasticSearch client (in milliseconds). | SW_STORAGE_ES_SOCKET_TIMEOUT | 30000|
-| - | - | user| Username of ElasticSearch cluster.| SW_ES_USER | - |
-| - | - | password | Password of ElasticSearch cluster. | SW_ES_PASSWORD | - |
-| - | - | trustStorePath | Trust JKS file path. Only works when username and password are enabled. | SW_STORAGE_ES_SSL_JKS_PATH | - |
-| - | - | trustStorePass | Trust JKS file password. Only works when username and password are enabled. | SW_STORAGE_ES_SSL_JKS_PASS | - |
-| - | - | secretsManagementFile| Secrets management file in the properties format, including username and password, which are managed by a 3rd party tool. Capable of being updated at runtime. |SW_ES_SECRETS_MANAGEMENT_FILE | - |
-| - | - | dayStep| Represents the number of days in the one-minute/hour/day index. | SW_STORAGE_DAY_STEP | 1|
-| - | - | indexShardsNumber | Shard number of new indexes. | SW_STORAGE_ES_INDEX_SHARDS_NUMBER | 1 |
-| - | - | indexReplicasNumber | Replicas number of new indexes. | SW_STORAGE_ES_INDEX_REPLICAS_NUMBER | 0 |
-| - | - | superDatasetDayStep | Represents the number of days in the super size dataset record index. Default value is the same as dayStep when the value is less than 0. |SW_SUPERDATASET_STORAGE_DAY_STEP|-1 |
-| - | - | superDatasetIndexShardsFactor | Super dataset is defined in the code (e.g. trace segments). This factor provides more shards for the super dataset: shards number = indexShardsNumber * superDatasetIndexShardsFactor. This factor also affects Zipkin and Jaeger traces. |SW_STORAGE_ES_SUPER_DATASET_INDEX_SHARDS_FACTOR|5 |
-| - | - | superDatasetIndexReplicasNumber | Represents the replicas number in the super size dataset record index. |SW_STORAGE_ES_SUPER_DATASET_INDEX_REPLICAS_NUMBER|0 |
-| - | - | indexTemplateOrder| The order of index template. | SW_STORAGE_ES_INDEX_TEMPLATE_ORDER| 0|
-| - | - | bulkActions| Async bulk size of data batch execution. | SW_STORAGE_ES_BULK_ACTIONS| 5000|
-| - | - | flushInterval| Period of flush (in seconds). Does not matter whether `bulkActions` is reached or not. INT(flushInterval * 2/3) is used for index refresh period. | SW_STORAGE_ES_FLUSH_INTERVAL | 15 (index refresh period = 10)|
-| - | - | concurrentRequests| The number of concurrent requests allowed to be executed. | SW_STORAGE_ES_CONCURRENT_REQUESTS| 2 |
-| - | - | resultWindowMaxSize | The maximum size of dataset when the OAP loads cache, such as network aliases. | SW_STORAGE_ES_QUERY_MAX_WINDOW_SIZE | 10000|
-| - | - | metadataQueryMaxSize | The maximum size of metadata per query. | SW_STORAGE_ES_QUERY_MAX_SIZE | 5000 |
-| - | - | segmentQueryMaxSize | The maximum size of trace segments per query. | SW_STORAGE_ES_QUERY_SEGMENT_SIZE | 200|
-| - | - | profileTaskQueryMaxSize | The maximum size of profile task per query. | SW_STORAGE_ES_QUERY_PROFILE_TASK_SIZE | 200|
-| - | - | advanced | All settings of ElasticSearch index creation. The value should be in JSON format. | SW_STORAGE_ES_ADVANCED | - |
 | - |h2| - |  H2 storage is designed for demonstration and running in short term (i.e. 1-2 hours) only. | - | - |
 | - | - | driver | H2 JDBC driver. | SW_STORAGE_H2_DRIVER | org.h2.jdbcx.JdbcDataSource|
 | - | - | url | H2 connection URL. Defaults to H2 memory mode. | SW_STORAGE_H2_URL | jdbc:h2:mem:skywalking-oap-db |
diff --git a/docs/en/setup/backend/spring-sleuth-setup.md b/docs/en/setup/backend/spring-sleuth-setup.md
index 16c3ccc..de387a0 100644
--- a/docs/en/setup/backend/spring-sleuth-setup.md
+++ b/docs/en/setup/backend/spring-sleuth-setup.md
@@ -35,7 +35,7 @@ receiver-meter:
   default:
 ```
 
-2. Configure the meter config file. It already has the [spring sleuth meter config](../../../../oap-server/server-bootstrap/src/main/resources/meter-analyzer-config/spring-sleuth.yaml).
+2. Configure the meter config file. It already has the [spring sleuth meter config](../../../../oap-server/server-starter/src/main/resources/meter-analyzer-config/spring-sleuth.yaml).
 If you have a customized meter at the agent side, please configure the meter using the steps set out in the [meter document](backend-meter.md#meters-configure).
    
 3. Enable Spring sleuth config in `application.yml`.
diff --git a/docs/en/setup/envoy/als_setting.md b/docs/en/setup/envoy/als_setting.md
index 93ff4ed..33e04cc 100644
--- a/docs/en/setup/envoy/als_setting.md
+++ b/docs/en/setup/envoy/als_setting.md
@@ -96,7 +96,7 @@ The [blog](https://skywalking.apache.org/blog/obs-service-mesh-vm-with-sw-and-al
 `persistence` analyzer adapts the Envoy access log format to
 SkyWalking's [native log format](https://github.com/apache/skywalking-data-collect-protocol/blob/master/logging/Logging.proto), and forwards the formatted logs to [LAL](../../concepts-and-designs/lal.md), where you can configure persistent
 conditions, such as `sampler`, only persist error logs, etc. SkyWalking provides a default configuration
-file [`envoy-als.yaml`](../../../../oap-server/server-bootstrap/src/main/resources/lal/envoy-als.yaml) that you can
+file [`envoy-als.yaml`](../../../../oap-server/server-starter/src/main/resources/lal/envoy-als.yaml) that you can
 adjust as per your needs. Please make sure to activate this rule via adding the rule name `envoy-als`
 into config item `log-analyzer/default/lalFiles` (or environment variable `SW_LOG_LAL_FILES`,
 e.g. `SW_LOG_LAL_FILES=envoy-als`).
diff --git a/docs/en/setup/envoy/examples/metrics/log4j2.xml b/docs/en/setup/envoy/examples/metrics/log4j2.xml
index 6c3774e..9525011 100644
--- a/docs/en/setup/envoy/examples/metrics/log4j2.xml
+++ b/docs/en/setup/envoy/examples/metrics/log4j2.xml
@@ -26,7 +26,6 @@
     <Loggers>
         <logger name="org.eclipse.jetty" level="INFO"/>
         <logger name="org.apache.zookeeper" level="INFO"/>
-        <logger name="org.elasticsearch.common.network.IfConfig" level="INFO"/>
         <logger name="io.grpc.netty" level="INFO"/>
         <logger name="org.apache.skywalking.oap.server.receiver.istio.telemetry" level="DEBUG"/>
         <!-- We make envoy metrics receiver to log at DEBUG level -->
diff --git a/oap-server-bom-es7/pom.xml b/oap-server-bom-es7/pom.xml
deleted file mode 100644
index f2de14f..0000000
--- a/oap-server-bom-es7/pom.xml
+++ /dev/null
@@ -1,56 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-  ~ 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.
-  ~
-  -->
-
-<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">
-    <parent>
-        <artifactId>apm</artifactId>
-        <groupId>org.apache.skywalking</groupId>
-        <version>8.8.0-SNAPSHOT</version>
-    </parent>
-    <packaging>pom</packaging>
-    <modelVersion>4.0.0</modelVersion>
-    <artifactId>oap-server-bom-es7</artifactId>
-
-    <properties>
-        <elasticsearch.version>7.10.2</elasticsearch.version>
-    </properties>
-
-    <dependencyManagement>
-        <dependencies>
-            <dependency>
-                <groupId>org.elasticsearch.client</groupId>
-                <artifactId>elasticsearch-rest-high-level-client</artifactId>
-                <version>${elasticsearch.version}</version>
-                <exclusions>
-                    <exclusion>
-                        <groupId>commons-logging</groupId>
-                        <artifactId>commons-logging</artifactId>
-                    </exclusion>
-                </exclusions>
-            </dependency>
-            <dependency>
-                <groupId>org.apache.skywalking</groupId>
-                <artifactId>oap-server-bom</artifactId>
-                <version>${project.version}</version>
-                <type>pom</type>
-                <scope>import</scope>
-            </dependency>
-        </dependencies>
-    </dependencyManagement>
-</project>
diff --git a/oap-server-bom/pom.xml b/oap-server-bom/pom.xml
index 5b4e179..ae8fc09 100644
--- a/oap-server-bom/pom.xml
+++ b/oap-server-bom/pom.xml
@@ -33,9 +33,9 @@
         <graphql-java-tools.version>5.2.3</graphql-java-tools.version>
         <graphql-java.version>8.0</graphql-java.version>
         <okhttp.version>3.14.9</okhttp.version>
+        <httpclient.version>4.5.13</httpclient.version>
         <h2.version>1.4.196</h2.version>
         <joda-time.version>2.10.5</joda-time.version>
-        <elasticsearch.version>6.3.2</elasticsearch.version>
         <zookeeper.version>3.5.7</zookeeper.version>
         <guava.version>28.1-jre</guava.version>
         <snakeyaml.version>1.28</snakeyaml.version>
@@ -73,6 +73,9 @@
         <postgresql.version>42.2.18</postgresql.version>
         <jetcd.version>0.5.3</jetcd.version>
         <testcontainers.version>1.15.3</testcontainers.version>
+        <armeria.version>1.11.0</armeria.version>
+        <awaitility.version>3.0.0</awaitility.version>
+        <httpcore.version>4.4.13</httpcore.version>
     </properties>
 
     <dependencyManagement>
@@ -140,17 +143,6 @@
                 <version>${joda-time.version}</version>
             </dependency>
             <dependency>
-                <groupId>org.elasticsearch.client</groupId>
-                <artifactId>elasticsearch-rest-high-level-client</artifactId>
-                <version>${elasticsearch.version}</version>
-                <exclusions>
-                    <exclusion>
-                        <groupId>commons-logging</groupId>
-                        <artifactId>commons-logging</artifactId>
-                    </exclusion>
-                </exclusions>
-            </dependency>
-            <dependency>
                 <groupId>org.apache.zookeeper</groupId>
                 <artifactId>zookeeper</artifactId>
                 <version>${zookeeper.version}</version>
@@ -480,6 +472,12 @@
             </dependency>
 
             <dependency>
+                <groupId>org.apache.httpcomponents</groupId>
+                <artifactId>httpclient</artifactId>
+                <version>${httpclient.version}</version>
+            </dependency>
+
+            <dependency>
                 <groupId>com.google.flatbuffers</groupId>
                 <artifactId>flatbuffers-java</artifactId>
                 <version>${flatbuffers-java.version}</version>
@@ -492,11 +490,49 @@
             </dependency>
 
             <dependency>
+                <groupId>com.linecorp.armeria</groupId>
+                <artifactId>armeria</artifactId>
+                <version>${armeria.version}</version>
+                <exclusions>
+                    <exclusion>
+                        <groupId>com.aayushatharva.brotli4j</groupId>
+                        <artifactId>native-linux-x86_64</artifactId>
+                    </exclusion>
+                    <exclusion>
+                        <groupId>com.aayushatharva.brotli4j</groupId>
+                        <artifactId>native-osx-x86_64</artifactId>
+                    </exclusion>
+                </exclusions>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.httpcomponents</groupId>
+                <artifactId>httpcore</artifactId>
+                <version>${httpcore.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.httpcomponents</groupId>
+                <artifactId>httpcore-nio</artifactId>
+                <version>${httpcore.version}</version>
+            </dependency>
+
+            <dependency>
                 <groupId>org.testcontainers</groupId>
                 <artifactId>testcontainers</artifactId>
                 <version>${testcontainers.version}</version>
                 <scope>test</scope>
             </dependency>
+            <dependency>
+                <groupId>org.testcontainers</groupId>
+                <artifactId>elasticsearch</artifactId>
+                <version>${testcontainers.version}</version>
+                <scope>test</scope>
+            </dependency>
+            <dependency>
+                <groupId>org.awaitility</groupId>
+                <artifactId>awaitility</artifactId>
+                <version>${awaitility.version}</version>
+                <scope>test</scope>
+            </dependency>
         </dependencies>
     </dependencyManagement>
 </project>
diff --git a/oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/Analyzer.java b/oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/Analyzer.java
index 5364b0c..cd803e8 100644
--- a/oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/Analyzer.java
+++ b/oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/Analyzer.java
@@ -23,10 +23,15 @@ import io.vavr.Tuple;
 import io.vavr.Tuple2;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 import lombok.AccessLevel;
 import lombok.RequiredArgsConstructor;
 import lombok.ToString;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.skywalking.oap.meter.analyzer.dsl.DSL;
 import org.apache.skywalking.oap.meter.analyzer.dsl.DownsamplingType;
 import org.apache.skywalking.oap.meter.analyzer.dsl.Expression;
@@ -50,12 +55,6 @@ import org.apache.skywalking.oap.server.core.analysis.meter.function.BucketedVal
 import org.apache.skywalking.oap.server.core.analysis.meter.function.PercentileArgument;
 import org.apache.skywalking.oap.server.core.analysis.metrics.DataTable;
 import org.apache.skywalking.oap.server.core.analysis.worker.MetricsStreamProcessor;
-import org.elasticsearch.common.Strings;
-
-import java.util.Objects;
-import java.util.function.Predicate;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
 
 import static java.util.Objects.requireNonNull;
 import static java.util.stream.Collectors.groupingBy;
@@ -232,7 +231,7 @@ public class Analyzer {
                               final String dataType,
                               final DownsamplingType downsamplingType) {
         String functionName = String.format(
-            FUNCTION_NAME_TEMP, downsamplingType.toString().toLowerCase(), Strings.capitalize(dataType));
+            FUNCTION_NAME_TEMP, downsamplingType.toString().toLowerCase(), StringUtils.capitalize(dataType));
         meterSystem.create(metricName, functionName, scopeType);
     }
 
diff --git a/oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/MetricConvert.java b/oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/MetricConvert.java
index 599ef09..c7bec66 100644
--- a/oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/MetricConvert.java
+++ b/oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/MetricConvert.java
@@ -19,6 +19,7 @@
 package org.apache.skywalking.oap.meter.analyzer;
 
 import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableMap;
 import io.vavr.control.Try;
 import java.util.List;
@@ -27,7 +28,6 @@ import java.util.stream.Stream;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.skywalking.oap.meter.analyzer.dsl.SampleFamily;
 import org.apache.skywalking.oap.server.core.analysis.meter.MeterSystem;
-import org.elasticsearch.common.Strings;
 
 import static java.util.stream.Collectors.toList;
 
@@ -51,7 +51,7 @@ public class MetricConvert {
         this.analyzers = rule.getMetricsRules().stream().map(
             r -> Analyzer.build(
                 formatMetricName(rule, r.getName()),
-                Strings.isEmpty(rule.getExpSuffix()) ?
+                Strings.isNullOrEmpty(rule.getExpSuffix()) ?
                     r.getExp() : String.format("(%s).%s", r.getExp(), rule.getExpSuffix()),
                 service
             )
diff --git a/oap-server/exporter/src/test/java/org/apache/skywalking/oap/server/exporter/provider/grpc/GRPCExporterTest.java b/oap-server/exporter/src/test/java/org/apache/skywalking/oap/server/exporter/provider/grpc/GRPCExporterTest.java
index 5a2fd05..480632f 100644
--- a/oap-server/exporter/src/test/java/org/apache/skywalking/oap/server/exporter/provider/grpc/GRPCExporterTest.java
+++ b/oap-server/exporter/src/test/java/org/apache/skywalking/oap/server/exporter/provider/grpc/GRPCExporterTest.java
@@ -90,7 +90,7 @@ public class GRPCExporterTest {
 
     @Test
     public void onError() {
-        Exception e = new IllegalArgumentException("some something wrong");
+        Exception e = new IllegalArgumentException("something wrong");
         exporter.onError(Collections.emptyList(), e);
         exporter.onError(dataList(), e);
     }
@@ -108,4 +108,4 @@ public class GRPCExporterTest {
         dataList.add(new ExportData(metaInfo, new MockDoubleValueMetrics(), INCREMENT));
         return dataList;
     }
-}
\ No newline at end of file
+}
diff --git a/oap-server/pom.xml b/oap-server/pom.xml
index 6318e02..4d464a8 100755
--- a/oap-server/pom.xml
+++ b/oap-server/pom.xml
@@ -35,7 +35,6 @@
         <module>server-storage-plugin</module>
         <module>server-library</module>
         <module>server-starter</module>
-        <module>server-starter-es7</module>
         <module>server-query-plugin</module>
         <module>server-alarm-plugin</module>
         <module>server-testing</module>
@@ -44,7 +43,6 @@
         <module>oal-grammar</module>
         <module>exporter</module>
         <module>server-configuration</module>
-        <module>server-bootstrap</module>
         <module>server-tools</module>
         <module>server-fetcher-plugin</module>
         <module>server-health-checker</module>
diff --git a/oap-server/server-alarm-plugin/pom.xml b/oap-server/server-alarm-plugin/pom.xml
index 45f7902..ac86ac6 100644
--- a/oap-server/server-alarm-plugin/pom.xml
+++ b/oap-server/server-alarm-plugin/pom.xml
@@ -51,6 +51,10 @@
             <groupId>org.mvel</groupId>
             <artifactId>mvel2</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+        </dependency>
     </dependencies>
 
     <build>
@@ -83,4 +87,4 @@
         </plugins>
     </build>
 
-</project>
\ No newline at end of file
+</project>
diff --git a/oap-server/server-alarm-plugin/src/test/resources/log4j2-test.xml b/oap-server/server-alarm-plugin/src/test/resources/log4j2-test.xml
index b440b5e..e819176 100644
--- a/oap-server/server-alarm-plugin/src/test/resources/log4j2-test.xml
+++ b/oap-server/server-alarm-plugin/src/test/resources/log4j2-test.xml
@@ -26,7 +26,6 @@
     <Loggers>
         <logger name="org.eclipse.jetty" level="INFO"/>
         <logger name="org.apache.zookeeper" level="INFO"/>
-        <logger name="org.elasticsearch.common.network.IfConfig" level="INFO"/>
         <logger name="io.grpc.netty" level="INFO"/>
         <logger name="org.apache.skywalking.oap.server.core.alarm.provider" level="TRACE"/>
         <Root level="INFO">
diff --git a/oap-server/server-bootstrap/pom.xml b/oap-server/server-bootstrap/pom.xml
deleted file mode 100644
index 9a8db46..0000000
--- a/oap-server/server-bootstrap/pom.xml
+++ /dev/null
@@ -1,295 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-  ~ 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.
-  ~
-  -->
-
-<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">
-    <parent>
-        <artifactId>oap-server</artifactId>
-        <groupId>org.apache.skywalking</groupId>
-        <version>8.8.0-SNAPSHOT</version>
-    </parent>
-    <modelVersion>4.0.0</modelVersion>
-
-    <artifactId>server-bootstrap</artifactId>
-
-    <dependencies>
-        <dependency>
-            <groupId>org.apache.skywalking</groupId>
-            <artifactId>server-core</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-
-        <!-- OAL runtime core -->
-        <dependency>
-            <groupId>org.apache.skywalking</groupId>
-            <artifactId>oal-rt</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <!-- OAL runtime core -->
-
-        <!-- cluster module -->
-        <dependency>
-            <groupId>org.apache.skywalking</groupId>
-            <artifactId>cluster-standalone-plugin</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.skywalking</groupId>
-            <artifactId>cluster-zookeeper-plugin</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.skywalking</groupId>
-            <artifactId>cluster-kubernetes-plugin</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.skywalking</groupId>
-            <artifactId>cluster-consul-plugin</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.skywalking</groupId>
-            <artifactId>cluster-etcd-plugin</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.skywalking</groupId>
-            <artifactId>cluster-nacos-plugin</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <!-- cluster module -->
-
-        <!-- receiver module -->
-        <dependency>
-            <groupId>org.apache.skywalking</groupId>
-            <artifactId>skywalking-mesh-receiver-plugin</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.skywalking</groupId>
-            <artifactId>skywalking-management-receiver-plugin</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.skywalking</groupId>
-            <artifactId>skywalking-jvm-receiver-plugin</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.skywalking</groupId>
-            <artifactId>skywalking-trace-receiver-plugin</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.skywalking</groupId>
-            <artifactId>envoy-metrics-receiver-plugin</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.skywalking</groupId>
-            <artifactId>skywalking-clr-receiver-plugin</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.skywalking</groupId>
-            <artifactId>skywalking-profile-receiver-plugin</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.skywalking</groupId>
-            <artifactId>otel-receiver-plugin</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.skywalking</groupId>
-            <artifactId>skywalking-meter-receiver-plugin</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.skywalking</groupId>
-            <artifactId>skywalking-browser-receiver-plugin</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.skywalking</groupId>
-            <artifactId>skywalking-log-recevier-plugin</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.skywalking</groupId>
-            <artifactId>configuration-discovery-receiver-plugin</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.skywalking</groupId>
-            <artifactId>skywalking-event-receiver-plugin</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.skywalking</groupId>
-            <artifactId>skywalking-zabbix-receiver-plugin</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <!-- receiver module -->
-
-        <!-- fetcher module -->
-        <dependency>
-            <groupId>org.apache.skywalking</groupId>
-            <artifactId>prometheus-fetcher-plugin</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.skywalking</groupId>
-            <artifactId>kafka-fetcher-plugin</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <!-- fetcher module -->
-
-        <!-- storage module -->
-        <dependency>
-            <groupId>org.apache.skywalking</groupId>
-            <artifactId>storage-jdbc-hikaricp-plugin</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.skywalking</groupId>
-            <artifactId>storage-influxdb-plugin</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.skywalking</groupId>
-            <artifactId>storage-tidb-plugin</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <!-- storage module -->
-
-        <!-- queryBuild module -->
-        <dependency>
-            <groupId>org.apache.skywalking</groupId>
-            <artifactId>query-graphql-plugin</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <!-- queryBuild module -->
-
-        <!-- alarm module -->
-        <dependency>
-            <groupId>org.apache.skywalking</groupId>
-            <artifactId>server-alarm-plugin</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-
-        <!-- telemetry -->
-        <dependency>
-            <groupId>org.apache.skywalking</groupId>
-            <artifactId>telemetry-prometheus</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-
-        <!-- exporter -->
-        <dependency>
-            <groupId>org.apache.skywalking</groupId>
-            <artifactId>exporter</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-
-        <!-- configuration -->
-        <dependency>
-            <groupId>org.apache.skywalking</groupId>
-            <artifactId>grpc-configuration-sync</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-
-        <dependency>
-            <groupId>org.apache.skywalking</groupId>
-            <artifactId>configuration-apollo</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.skywalking</groupId>
-            <artifactId>configuration-zookeeper</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.skywalking</groupId>
-            <artifactId>configuration-etcd</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.skywalking</groupId>
-            <artifactId>configuration-consul</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.skywalking</groupId>
-            <artifactId>configuration-k8s-configmap</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.skywalking</groupId>
-            <artifactId>configuration-nacos</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-    </dependencies>
-
-    <build>
-        <plugins>
-            <plugin>
-                <artifactId>maven-compiler-plugin</artifactId>
-                <configuration>
-                    <source>${compiler.version}</source>
-                    <target>${compiler.version}</target>
-                    <encoding>${project.build.sourceEncoding}</encoding>
-                </configuration>
-            </plugin>
-            <plugin>
-                <groupId>org.apache.maven.plugins</groupId>
-                <artifactId>maven-resources-plugin</artifactId>
-                <configuration>
-                    <encoding>${project.build.sourceEncoding}</encoding>
-                </configuration>
-            </plugin>
-            <plugin>
-                <artifactId>maven-jar-plugin</artifactId>
-                <configuration>
-                    <excludes>
-                        <exclude>application.yml</exclude>
-                        <exclude>log4j2.xml</exclude>
-                        <exclude>alarm-settings.yml</exclude>
-                        <exclude>component-libraries.yml</exclude>
-                        <exclude>gateways.yml</exclude>
-                        <exclude>service-apdex-threshold.yml</exclude>
-                        <exclude>endpoint-name-grouping.yml</exclude>
-                        <exclude>metadata-service-mapping.yaml</exclude>
-                        <exclude>trace-sampling-policy-settings.yml</exclude>
-                        <exclude>oal/</exclude>
-                        <exclude>fetcher-prom-rules/</exclude>
-                        <exclude>envoy-metrics-rules/</exclude>
-                        <exclude>meter-analyzer-config/</exclude>
-                        <exclude>openapi-definitions/</exclude>
-                        <exclude>otel-oc-rules/</exclude>
-                        <exclude>ui-initialized-templates/</exclude>
-                        <exclude>zabbix-rules/</exclude>
-                        <exclude>lal/</exclude>
-                        <exclude>log-mal-rules/</exclude>
-                    </excludes>
-                </configuration>
-            </plugin>
-        </plugins>
-    </build>
-</project>
diff --git a/oap-server/server-cluster-plugin/cluster-consul-plugin/src/test/java/org/apache/skywalking/oap/server/cluster/plugin/consul/ITClusterModuleConsulProviderFunctionalTest.java b/oap-server/server-cluster-plugin/cluster-consul-plugin/src/test/java/org/apache/skywalking/oap/server/cluster/plugin/consul/ITClusterModuleConsulProviderFunctionalTest.java
index caef939..94963b9 100644
--- a/oap-server/server-cluster-plugin/cluster-consul-plugin/src/test/java/org/apache/skywalking/oap/server/cluster/plugin/consul/ITClusterModuleConsulProviderFunctionalTest.java
+++ b/oap-server/server-cluster-plugin/cluster-consul-plugin/src/test/java/org/apache/skywalking/oap/server/cluster/plugin/consul/ITClusterModuleConsulProviderFunctionalTest.java
@@ -50,7 +50,7 @@ import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 @RunWith(PowerMockRunner.class)
-@PowerMockIgnore({"javax.net.ssl.*", "javax.security.*"})
+@PowerMockIgnore({"javax.net.ssl.*", "javax.security.*", "javax.management.*"})
 public class ITClusterModuleConsulProviderFunctionalTest {
 
     private String consulAddress;
diff --git a/oap-server/server-cluster-plugin/cluster-etcd-plugin/pom.xml b/oap-server/server-cluster-plugin/cluster-etcd-plugin/pom.xml
index 1a19347..e46c021 100644
--- a/oap-server/server-cluster-plugin/cluster-etcd-plugin/pom.xml
+++ b/oap-server/server-cluster-plugin/cluster-etcd-plugin/pom.xml
@@ -86,26 +86,4 @@
             <artifactId>testcontainers</artifactId>
         </dependency>
     </dependencies>
-
-    <profiles>
-        <profile>
-            <id>CI-with-IT</id>
-            <build>
-                <plugins>
-                    <plugin>
-                        <groupId>org.apache.maven.plugins</groupId>
-                        <artifactId>maven-failsafe-plugin</artifactId>
-                        <executions>
-                            <execution>
-                                <goals>
-                                    <goal>integration-test</goal>
-                                    <goal>verify</goal>
-                                </goals>
-                            </execution>
-                        </executions>
-                    </plugin>
-                </plugins>
-            </build>
-        </profile>
-    </profiles>
 </project>
diff --git a/oap-server/server-cluster-plugin/cluster-etcd-plugin/src/main/java/org/apache/skywalking/oap/server/cluster/plugin/etcd/EtcdCoordinator.java b/oap-server/server-cluster-plugin/cluster-etcd-plugin/src/main/java/org/apache/skywalking/oap/server/cluster/plugin/etcd/EtcdCoordinator.java
index aceb581..49ada8f 100644
--- a/oap-server/server-cluster-plugin/cluster-etcd-plugin/src/main/java/org/apache/skywalking/oap/server/cluster/plugin/etcd/EtcdCoordinator.java
+++ b/oap-server/server-cluster-plugin/cluster-etcd-plugin/src/main/java/org/apache/skywalking/oap/server/cluster/plugin/etcd/EtcdCoordinator.java
@@ -173,7 +173,7 @@ public class EtcdCoordinator implements ClusterRegister, ClusterNodesQuery {
             });
         } catch (Throwable e) {
             healthChecker.unHealth(e);
-            throw new ServiceRegisterException(e.getMessage());
+            throw new ServiceRegisterException(e);
         }
     }
 
diff --git a/oap-server/server-cluster-plugin/cluster-etcd-plugin/src/test/java/org/apache/skywalking/oap/server/cluster/plugin/etcd/ITClusterEtcdPluginTest.java b/oap-server/server-cluster-plugin/cluster-etcd-plugin/src/test/java/org/apache/skywalking/oap/server/cluster/plugin/etcd/ITClusterEtcdPluginTest.java
index 6f1e4e6..b829165 100644
--- a/oap-server/server-cluster-plugin/cluster-etcd-plugin/src/test/java/org/apache/skywalking/oap/server/cluster/plugin/etcd/ITClusterEtcdPluginTest.java
+++ b/oap-server/server-cluster-plugin/cluster-etcd-plugin/src/test/java/org/apache/skywalking/oap/server/cluster/plugin/etcd/ITClusterEtcdPluginTest.java
@@ -31,7 +31,7 @@ import org.apache.skywalking.oap.server.core.remote.client.Address;
 import org.apache.skywalking.oap.server.library.module.ModuleDefineHolder;
 import org.apache.skywalking.oap.server.telemetry.api.HealthCheckMetrics;
 import org.junit.Before;
-import org.junit.ClassRule;
+import org.junit.Rule;
 import org.junit.Test;
 import org.powermock.reflect.Whitebox;
 import org.testcontainers.containers.GenericContainer;
@@ -49,7 +49,7 @@ public class ITClusterEtcdPluginTest {
 
     private Client client;
 
-    private HealthCheckMetrics healthChecker = mock(HealthCheckMetrics.class);
+    private final HealthCheckMetrics healthChecker = mock(HealthCheckMetrics.class);
 
     private EtcdCoordinator coordinator;
 
@@ -59,15 +59,22 @@ public class ITClusterEtcdPluginTest {
 
     private static final String SERVICE_NAME = "my-service";
 
-    @ClassRule
-    public static final GenericContainer CONTAINER =
-        new GenericContainer(DockerImageName.parse("bitnami/etcd:3.5.0"))
-            .waitingFor(Wait.forLogMessage(".*etcd setup finished!.*", 1))
-            .withEnv(Collections.singletonMap("ALLOW_NONE_AUTHENTICATION", "yes"));
+    @Rule
+    public final GenericContainer<?> container =
+        new GenericContainer<>(DockerImageName.parse("quay.io/coreos/etcd:v3.5.0"))
+            .waitingFor(Wait.forLogMessage(".*ready to serve client requests.*", 1))
+            .withEnv(Collections.singletonMap("ALLOW_NONE_AUTHENTICATION", "yes"))
+            .withCommand(
+                "etcd",
+                "--advertise-client-urls", "http://0.0.0.0:2379",
+                "--listen-client-urls", "http://0.0.0.0:2379"
+            );
 
     @Before
     public void before() throws Exception {
-        String baseUrl = "http://127.0.0.1:" + CONTAINER.getMappedPort(2379);
+        String baseUrl = "http://" + container.getHost() + ":" + container.getMappedPort(2379);
+        System.setProperty("etcd.endpoint", baseUrl);
+
         etcdConfig = new ClusterModuleEtcdConfig();
         etcdConfig.setEndpoints(baseUrl);
         etcdConfig.setNamespace("skywalking/");
diff --git a/oap-server/server-cluster-plugin/cluster-etcd-plugin/src/test/java/org/apache/skywalking/oap/server/cluster/plugin/etcd/ITClusterModuleEtcdProviderFunctionalTest.java b/oap-server/server-cluster-plugin/cluster-etcd-plugin/src/test/java/org/apache/skywalking/oap/server/cluster/plugin/etcd/ITClusterModuleEtcdProviderFunctionalTest.java
index adc841b..fae8409 100644
--- a/oap-server/server-cluster-plugin/cluster-etcd-plugin/src/test/java/org/apache/skywalking/oap/server/cluster/plugin/etcd/ITClusterModuleEtcdProviderFunctionalTest.java
+++ b/oap-server/server-cluster-plugin/cluster-etcd-plugin/src/test/java/org/apache/skywalking/oap/server/cluster/plugin/etcd/ITClusterModuleEtcdProviderFunctionalTest.java
@@ -34,7 +34,7 @@ import org.apache.skywalking.oap.server.telemetry.api.MetricsCreator;
 import org.apache.skywalking.oap.server.telemetry.none.MetricsCreatorNoop;
 import org.apache.skywalking.oap.server.telemetry.none.NoneTelemetryProvider;
 import org.junit.Before;
-import org.junit.ClassRule;
+import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.Mockito;
 import org.powermock.reflect.Whitebox;
@@ -49,20 +49,26 @@ import static org.mockito.Mockito.mock;
 
 public class ITClusterModuleEtcdProviderFunctionalTest {
 
-    private static String ENDPOINTS;
-    private static NoneTelemetryProvider TELEMETRY_PROVIDER = mock(NoneTelemetryProvider.class);
+    private String endpoint;
+    private NoneTelemetryProvider telemetryProvider;
 
-    @ClassRule
-    public static final GenericContainer CONTAINER =
-        new GenericContainer(DockerImageName.parse("bitnami/etcd:3.5.0"))
-            .waitingFor(Wait.forLogMessage(".*etcd setup finished!.*", 1))
-            .withEnv(Collections.singletonMap("ALLOW_NONE_AUTHENTICATION", "yes"));
+    @Rule
+    public final GenericContainer<?> container =
+        new GenericContainer<>(DockerImageName.parse("quay.io/coreos/etcd:v3.5.0"))
+            .waitingFor(Wait.forLogMessage(".*ready to serve client requests.*", 1))
+            .withEnv(Collections.singletonMap("ALLOW_NONE_AUTHENTICATION", "yes"))
+            .withCommand(
+                "etcd",
+                "--advertise-client-urls", "http://0.0.0.0:2379",
+                "--listen-client-urls", "http://0.0.0.0:2379"
+            );
 
     @Before
     public void setup() {
-        Mockito.when(TELEMETRY_PROVIDER.getService(MetricsCreator.class))
+        telemetryProvider = mock(NoneTelemetryProvider.class);
+        Mockito.when(telemetryProvider.getService(MetricsCreator.class))
                .thenReturn(new MetricsCreatorNoop());
-        ENDPOINTS = "http://127.0.0.1:" + CONTAINER.getMappedPort(2379);
+        endpoint = "http://" + container.getHost() + ":" + container.getMappedPort(2379);
     }
 
     @Test
@@ -92,7 +98,8 @@ public class ITClusterModuleEtcdProviderFunctionalTest {
 
         List<RemoteInstance> remoteInstances = queryRemoteNodes(provider, 1);
 
-        ClusterModuleEtcdConfig config = (ClusterModuleEtcdConfig) provider.createConfigBeanIfAbsent();
+        ClusterModuleEtcdConfig config =
+            (ClusterModuleEtcdConfig) provider.createConfigBeanIfAbsent();
         assertEquals(1, remoteInstances.size());
         Address queryAddress = remoteInstances.get(0).getAddress();
         assertEquals(config.getInternalComHost(), queryAddress.getHost());
@@ -174,17 +181,20 @@ public class ITClusterModuleEtcdProviderFunctionalTest {
         assertTrue(addressB.isSelf());
     }
 
-    private ClusterModuleEtcdProvider createProvider(String serviceName) throws ModuleStartException {
+    private ClusterModuleEtcdProvider createProvider(String serviceName)
+        throws ModuleStartException {
         return createProvider(serviceName, null, 0);
     }
 
     private ClusterModuleEtcdProvider createProvider(String serviceName, String internalComHost,
-                                                     int internalComPort) throws ModuleStartException {
+                                                     int internalComPort)
+        throws ModuleStartException {
         ClusterModuleEtcdProvider provider = new ClusterModuleEtcdProvider();
 
-        ClusterModuleEtcdConfig config = (ClusterModuleEtcdConfig) provider.createConfigBeanIfAbsent();
+        ClusterModuleEtcdConfig config =
+            (ClusterModuleEtcdConfig) provider.createConfigBeanIfAbsent();
 
-        config.setEndpoints(ENDPOINTS);
+        config.setEndpoints(endpoint);
         config.setServiceName(serviceName);
 
         if (!StringUtil.isEmpty(internalComHost)) {
@@ -195,7 +205,7 @@ public class ITClusterModuleEtcdProviderFunctionalTest {
             config.setInternalComPort(internalComPort);
         }
         TelemetryModule telemetryModule = Mockito.spy(TelemetryModule.class);
-        Whitebox.setInternalState(telemetryModule, "loadedProvider", TELEMETRY_PROVIDER);
+        Whitebox.setInternalState(telemetryModule, "loadedProvider", telemetryProvider);
         ModuleManager manager = mock(ModuleManager.class);
         Mockito.when(manager.find(TelemetryModule.NAME)).thenReturn(telemetryModule);
 
@@ -214,7 +224,8 @@ public class ITClusterModuleEtcdProviderFunctionalTest {
         return provider.getService(ClusterNodesQuery.class);
     }
 
-    private List<RemoteInstance> queryRemoteNodes(ModuleProvider provider, int goals) throws InterruptedException {
+    private List<RemoteInstance> queryRemoteNodes(ModuleProvider provider, int goals)
+        throws InterruptedException {
         return queryRemoteNodes(provider, goals, 20);
     }
 
@@ -229,10 +240,11 @@ public class ITClusterModuleEtcdProviderFunctionalTest {
             }
         }
         while (--cyclic > 0);
-        return Collections.EMPTY_LIST;
+        return Collections.emptyList();
     }
 
-    private void validateServiceInstance(Address selfAddress, Address otherAddress, List<RemoteInstance> queryResult) {
+    private void validateServiceInstance(Address selfAddress, Address otherAddress,
+                                         List<RemoteInstance> queryResult) {
         assertEquals(2, queryResult.size());
 
         boolean selfExist = false, otherExist = false;
diff --git a/oap-server/server-cluster-plugin/cluster-nacos-plugin/pom.xml b/oap-server/server-cluster-plugin/cluster-nacos-plugin/pom.xml
index 0598222..ce47c3a 100644
--- a/oap-server/server-cluster-plugin/cluster-nacos-plugin/pom.xml
+++ b/oap-server/server-cluster-plugin/cluster-nacos-plugin/pom.xml
@@ -64,107 +64,17 @@
                     <groupId>net.jcip</groupId>
                     <artifactId>jcip-annotations</artifactId>
                 </exclusion>
-                <exclusion>
-                    <groupId>org.apache.httpcomponents</groupId>
-                    <artifactId>httpasyncclient</artifactId>
-                </exclusion>
             </exclusions>
         </dependency>
-    </dependencies>
-
-    <profiles>
-        <profile>
-            <id>CI-with-IT</id>
-            <build>
-                <plugins>
-                    <plugin>
-                        <groupId>io.fabric8</groupId>
-                        <artifactId>docker-maven-plugin</artifactId>
-                        <configuration>
-                            <sourceMode>all</sourceMode>
-                            <showLogs>true</showLogs>
-                            <logDate>default</logDate>
-                            <verbose>true</verbose>
-                            <imagePullPolicy>IfNotPresent</imagePullPolicy>
-                            <images>
-                                <image>
-                                    <name>nacos/nacos-server:${test.nacos.version}</name>
-                                    <alias>cluster-nacos-plugin-integration-test-nacos</alias>
-                                    <run>
-                                        <env>
-                                            <MODE>standalone</MODE>
-                                        </env>
-                                        <ports>
-                                            <port>nacos.port:8848</port>
-                                        </ports>
-                                        <wait>
-                                            <log>Nacos started successfully</log>
-                                            <time>120000</time>
-                                        </wait>
-                                    </run>
-                                </image>
-                            </images>
-                        </configuration>
-                        <executions>
-                            <execution>
-                                <id>start</id>
-                                <phase>pre-integration-test</phase>
-                                <goals>
-                                    <goal>start</goal>
-                                </goals>
-                            </execution>
-                            <execution>
-                                <id>stop</id>
-                                <phase>post-integration-test</phase>
-                                <goals>
-                                    <goal>stop</goal>
-                                </goals>
-                            </execution>
-                        </executions>
-                    </plugin>
-                    <plugin>
-                        <groupId>org.codehaus.gmaven</groupId>
-                        <artifactId>gmaven-plugin</artifactId>
-                        <version>${gmaven-plugin.version}</version>
-                        <executions>
-                            <execution>
-                                <id>add-default-properties</id>
-                                <phase>initialize</phase>
-                                <goals>
-                                    <goal>execute</goal>
-                                </goals>
-                                <configuration>
-                                    <providerSelection>2.0</providerSelection>
-                                    <source>
-                                        project.properties.setProperty('docker.hostname', 'localhost')
+        <!-- Override the dependency to use the same version of httpcore-nio -->
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpcore-nio</artifactId>
+        </dependency>
 
-                                        log.info("Docker hostname is " + project.properties['docker.hostname'])
-                                    </source>
-                                </configuration>
-                            </execution>
-                        </executions>
-                    </plugin>
-                    <plugin>
-                        <groupId>org.apache.maven.plugins</groupId>
-                        <artifactId>maven-failsafe-plugin</artifactId>
-                        <configuration>
-                            <systemPropertyVariables>
-                                <nacos.address>
-                                    ${docker.hostname}:${nacos.port}
-                                </nacos.address>
-                            </systemPropertyVariables>
-                        </configuration>
-                        <executions>
-                            <execution>
-                                <goals>
-                                    <goal>integration-test</goal>
-                                    <goal>verify</goal>
-                                </goals>
-                            </execution>
-                        </executions>
-                    </plugin>
-                </plugins>
-            </build>
-        </profile>
-    </profiles>
+        <dependency>
+            <groupId>org.testcontainers</groupId>
+            <artifactId>testcontainers</artifactId>
+        </dependency>
+    </dependencies>
 </project>
diff --git a/oap-server/server-cluster-plugin/cluster-nacos-plugin/src/test/java/org/apache/skywalking/oap/server/cluster/plugin/nacos/ITClusterModuleNacosProviderFunctionalTest.java b/oap-server/server-cluster-plugin/cluster-nacos-plugin/src/test/java/org/apache/skywalking/oap/server/cluster/plugin/nacos/ITClusterModuleNacosProviderFunctionalTest.java
index 87cba2a..774793b 100644
--- a/oap-server/server-cluster-plugin/cluster-nacos-plugin/src/test/java/org/apache/skywalking/oap/server/cluster/plugin/nacos/ITClusterModuleNacosProviderFunctionalTest.java
+++ b/oap-server/server-cluster-plugin/cluster-nacos-plugin/src/test/java/org/apache/skywalking/oap/server/cluster/plugin/nacos/ITClusterModuleNacosProviderFunctionalTest.java
@@ -21,7 +21,6 @@ package org.apache.skywalking.oap.server.cluster.plugin.nacos;
 import com.alibaba.nacos.api.naming.NamingService;
 import java.util.Collections;
 import java.util.List;
-
 import org.apache.skywalking.apm.util.StringUtil;
 import org.apache.skywalking.oap.server.core.cluster.ClusterNodesQuery;
 import org.apache.skywalking.oap.server.core.cluster.ClusterRegister;
@@ -35,25 +34,39 @@ import org.apache.skywalking.oap.server.telemetry.api.MetricsCreator;
 import org.apache.skywalking.oap.server.telemetry.none.MetricsCreatorNoop;
 import org.apache.skywalking.oap.server.telemetry.none.NoneTelemetryProvider;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.junit.runners.BlockJUnit4ClassRunner;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.powermock.core.classloader.annotations.PowerMockIgnore;
 import org.powermock.modules.junit4.PowerMockRunner;
+import org.powermock.modules.junit4.PowerMockRunnerDelegate;
 import org.powermock.reflect.Whitebox;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.wait.strategy.Wait;
+import org.testcontainers.utility.DockerImageName;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 @RunWith(PowerMockRunner.class)
-@PowerMockIgnore({"javax.security.*", "javax.net.ssl.*"})
+// ClassRule is not applied in PowerMockRunner
+@PowerMockRunnerDelegate(BlockJUnit4ClassRunner.class)
+@PowerMockIgnore({"javax.security.*", "javax.net.ssl.*", "javax.management.*"})
 public class ITClusterModuleNacosProviderFunctionalTest {
 
     private String nacosAddress;
-    private String username;
-    private String password;
+    private final String username = "nacos";
+    private final String password = "nacos";
+
+    @Rule
+    public final GenericContainer<?> container =
+        new GenericContainer<>(DockerImageName.parse("nacos/nacos-server:1.4.2"))
+            .waitingFor(Wait.forLogMessage(".*Nacos started successfully.*", 1))
+            .withEnv(Collections.singletonMap("MODE", "standalone"));
 
     @Mock
     private ModuleManager moduleManager;
@@ -62,15 +75,12 @@ public class ITClusterModuleNacosProviderFunctionalTest {
 
     @Before
     public void before() {
-        username = "nacos";
-        password = "nacos";
         Mockito.when(telemetryProvider.getService(MetricsCreator.class))
-                .thenReturn(new MetricsCreatorNoop());
+               .thenReturn(new MetricsCreatorNoop());
         TelemetryModule telemetryModule = Mockito.spy(TelemetryModule.class);
         Whitebox.setInternalState(telemetryModule, "loadedProvider", telemetryProvider);
         Mockito.when(moduleManager.find(TelemetryModule.NAME)).thenReturn(telemetryModule);
-        nacosAddress = System.getProperty("nacos.address");
-        assertFalse(StringUtil.isEmpty(nacosAddress));
+        nacosAddress = container.getHost() + ":" + container.getMappedPort(8848);
     }
 
     @Test
@@ -100,7 +110,8 @@ public class ITClusterModuleNacosProviderFunctionalTest {
 
         List<RemoteInstance> remoteInstances = queryRemoteNodes(provider, 1);
 
-        ClusterModuleNacosConfig config = (ClusterModuleNacosConfig) provider.createConfigBeanIfAbsent();
+        ClusterModuleNacosConfig config =
+            (ClusterModuleNacosConfig) provider.createConfigBeanIfAbsent();
         assertEquals(1, remoteInstances.size());
         Address queryAddress = remoteInstances.get(0).getAddress();
         assertEquals(config.getInternalComHost(), queryAddress.getHost());
@@ -183,10 +194,12 @@ public class ITClusterModuleNacosProviderFunctionalTest {
         assertTrue(address.isSelf());
     }
 
-    private ClusterModuleNacosProvider createProvider(String servicName) throws ModuleStartException {
+    private ClusterModuleNacosProvider createProvider(String servicName)
+        throws ModuleStartException {
         ClusterModuleNacosProvider provider = new ClusterModuleNacosProvider();
 
-        ClusterModuleNacosConfig config = (ClusterModuleNacosConfig) provider.createConfigBeanIfAbsent();
+        ClusterModuleNacosConfig config =
+            (ClusterModuleNacosConfig) provider.createConfigBeanIfAbsent();
 
         config.setHostPort(nacosAddress);
         config.setServiceName(servicName);
@@ -201,10 +214,11 @@ public class ITClusterModuleNacosProviderFunctionalTest {
     }
 
     private ClusterModuleNacosProvider createProvider(String serviceName, String internalComHost,
-                                                       int internalComPort) throws Exception {
+                                                      int internalComPort) throws Exception {
         ClusterModuleNacosProvider provider = new ClusterModuleNacosProvider();
 
-        ClusterModuleNacosConfig config = (ClusterModuleNacosConfig) provider.createConfigBeanIfAbsent();
+        ClusterModuleNacosConfig config =
+            (ClusterModuleNacosConfig) provider.createConfigBeanIfAbsent();
 
         config.setHostPort(nacosAddress);
         config.setServiceName(serviceName);
@@ -233,7 +247,8 @@ public class ITClusterModuleNacosProviderFunctionalTest {
         return provider.getService(ClusterNodesQuery.class);
     }
 
-    private List<RemoteInstance> queryRemoteNodes(ModuleProvider provider, int goals) throws InterruptedException {
+    private List<RemoteInstance> queryRemoteNodes(ModuleProvider provider, int goals)
+        throws InterruptedException {
         int i = 20;
         do {
             List<RemoteInstance> instances = getClusterNodesQuery(provider).queryRemoteNodes();
@@ -244,10 +259,11 @@ public class ITClusterModuleNacosProviderFunctionalTest {
             }
         }
         while (--i > 0);
-        return Collections.EMPTY_LIST;
+        return Collections.emptyList();
     }
 
-    private void validateServiceInstance(Address selfAddress, Address otherAddress, List<RemoteInstance> queryResult) {
+    private void validateServiceInstance(Address selfAddress, Address otherAddress,
+                                         List<RemoteInstance> queryResult) {
         assertEquals(2, queryResult.size());
 
         boolean selfExist = false, otherExist = false;
diff --git a/oap-server/server-configuration/configuration-apollo/pom.xml b/oap-server/server-configuration/configuration-apollo/pom.xml
index 1dff076..9b4439f 100644
--- a/oap-server/server-configuration/configuration-apollo/pom.xml
+++ b/oap-server/server-configuration/configuration-apollo/pom.xml
@@ -54,6 +54,11 @@
                 </exclusion>
             </exclusions>
         </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <profiles>
diff --git a/oap-server/server-configuration/configuration-etcd/pom.xml b/oap-server/server-configuration/configuration-etcd/pom.xml
index 372a3e1..dc00ee0 100644
--- a/oap-server/server-configuration/configuration-etcd/pom.xml
+++ b/oap-server/server-configuration/configuration-etcd/pom.xml
@@ -96,26 +96,4 @@
             </plugin>
         </plugins>
     </build>
-
-    <profiles>
-        <profile>
-            <id>CI-with-IT</id>
-            <build>
-                <plugins>
-                    <plugin>
-                        <groupId>org.apache.maven.plugins</groupId>
-                        <artifactId>maven-failsafe-plugin</artifactId>
-                        <executions>
-                            <execution>
-                                <goals>
-                                    <goal>integration-test</goal>
-                                    <goal>verify</goal>
-                                </goals>
-                            </execution>
-                        </executions>
-                    </plugin>
-                </plugins>
-            </build>
-        </profile>
-    </profiles>
 </project>
diff --git a/oap-server/server-configuration/configuration-etcd/src/test/java/org/apache/skywalking/oap/server/configuration/etcd/ITEtcdConfigurationTest.java b/oap-server/server-configuration/configuration-etcd/src/test/java/org/apache/skywalking/oap/server/configuration/etcd/ITEtcdConfigurationTest.java
index b207209..acbc77a 100644
--- a/oap-server/server-configuration/configuration-etcd/src/test/java/org/apache/skywalking/oap/server/configuration/etcd/ITEtcdConfigurationTest.java
+++ b/oap-server/server-configuration/configuration-etcd/src/test/java/org/apache/skywalking/oap/server/configuration/etcd/ITEtcdConfigurationTest.java
@@ -31,15 +31,11 @@ import java.util.concurrent.TimeUnit;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.skywalking.apm.util.PropertyPlaceholderHelper;
 import org.apache.skywalking.oap.server.library.module.ApplicationConfiguration;
-import org.apache.skywalking.oap.server.library.module.ModuleConfigException;
 import org.apache.skywalking.oap.server.library.module.ModuleManager;
-import org.apache.skywalking.oap.server.library.module.ModuleNotFoundException;
-import org.apache.skywalking.oap.server.library.module.ModuleStartException;
 import org.apache.skywalking.oap.server.library.util.CollectionUtils;
 import org.apache.skywalking.oap.server.library.util.ResourceUtils;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.ClassRule;
+import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.testcontainers.containers.GenericContainer;
 import org.testcontainers.containers.wait.strategy.Wait;
@@ -52,19 +48,22 @@ import static org.junit.Assert.assertNull;
 
 @Slf4j
 public class ITEtcdConfigurationTest {
-    @ClassRule
-    public static final GenericContainer CONTAINER =
-        new GenericContainer(DockerImageName.parse("bitnami/etcd:3.5.0"))
-            .waitingFor(Wait.forLogMessage(".*etcd setup finished!.*", 1))
-            .withEnv(Collections.singletonMap("ALLOW_NONE_AUTHENTICATION", "yes"));
-
-    private static EtcdConfigurationTestProvider PROVIDER;
-
-    private static final String TEST_VALUE = "value";
-
-    @BeforeClass
-    public static void beforeClass() throws FileNotFoundException, ModuleConfigException, ModuleNotFoundException, ModuleStartException {
-        System.setProperty("etcd.endpoint", "http://127.0.0.1:" + CONTAINER.getMappedPort(2379));
+    @Rule
+    public final GenericContainer<?> container =
+        new GenericContainer<>(DockerImageName.parse("quay.io/coreos/etcd:v3.5.0"))
+            .waitingFor(Wait.forLogMessage(".*ready to serve client requests.*", 1))
+            .withEnv(Collections.singletonMap("ALLOW_NONE_AUTHENTICATION", "yes"))
+            .withCommand(
+                "etcd",
+                "--advertise-client-urls", "http://0.0.0.0:2379",
+                "--listen-client-urls", "http://0.0.0.0:2379"
+            );
+
+    private EtcdConfigurationTestProvider provider;
+
+    @Before
+    public void before() throws Exception {
+        System.setProperty("etcd.endpoint", "http://127.0.0.1:" + container.getMappedPort(2379));
 
         final ApplicationConfiguration applicationConfiguration = new ApplicationConfiguration();
         loadConfig(applicationConfiguration);
@@ -72,40 +71,41 @@ public class ITEtcdConfigurationTest {
         final ModuleManager moduleManager = new ModuleManager();
         moduleManager.init(applicationConfiguration);
 
-        PROVIDER = (EtcdConfigurationTestProvider) moduleManager.find(EtcdConfigurationTestModule.NAME).provider();
+        provider = (EtcdConfigurationTestProvider) moduleManager.find(EtcdConfigurationTestModule.NAME).provider();
 
-        assertNotNull(PROVIDER);
+        assertNotNull(provider);
     }
 
     @Test(timeout = 20000)
     public void shouldReadUpdated() throws Exception {
-        assertNull(PROVIDER.watcher.value());
+        assertNull(provider.watcher.value());
 
         KV client = Client.builder()
-                          .endpoints("http://localhost:" + CONTAINER.getMappedPort(2379))
+                          .endpoints("http://localhost:" + container.getMappedPort(2379))
                           .namespace(ByteSequence.from("/skywalking/", Charset.defaultCharset()))
                           .build()
                           .getKVClient();
 
+        String testValue = "value";
         client.put(
             ByteSequence.from("test-module.default.testKey", Charset.defaultCharset()),
-            ByteSequence.from(TEST_VALUE, Charset.defaultCharset())
+            ByteSequence.from(testValue, Charset.defaultCharset())
         ).get();
 
-        for (String v = PROVIDER.watcher.value(); v == null; v = PROVIDER.watcher.value()) {
-            log.info("value is : {}", PROVIDER.watcher.value());
+        for (String v = provider.watcher.value(); v == null; v = provider.watcher.value()) {
+            log.info("value is : {}", provider.watcher.value());
             TimeUnit.MILLISECONDS.sleep(200L);
         }
 
-        assertEquals(TEST_VALUE, PROVIDER.watcher.value());
+        assertEquals(testValue, provider.watcher.value());
 
         client.delete(ByteSequence.from("test-module.default.testKey", Charset.defaultCharset())).get();
 
-        for (String v = PROVIDER.watcher.value(); v != null; v = PROVIDER.watcher.value()) {
+        for (String v = provider.watcher.value(); v != null; v = provider.watcher.value()) {
             TimeUnit.MILLISECONDS.sleep(200L);
         }
 
-        assertNull(PROVIDER.watcher.value());
+        assertNull(provider.watcher.value());
     }
 
     @SuppressWarnings("unchecked")
@@ -137,9 +137,4 @@ public class ITEtcdConfigurationTest {
             });
         }
     }
-
-    @AfterClass
-    public static void teardown() {
-        CONTAINER.close();
-    }
 }
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/cluster/ServiceRegisterException.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/cluster/ServiceRegisterException.java
index edb47d3..8580bb8 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/cluster/ServiceRegisterException.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/cluster/ServiceRegisterException.java
@@ -23,4 +23,8 @@ public class ServiceRegisterException extends RuntimeException {
     public ServiceRegisterException(String message) {
         super(message);
     }
+
+    public ServiceRegisterException(Throwable cause) {
+        super(cause);
+    }
 }
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/profile/analyze/ProfileAnalyzer.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/profile/analyze/ProfileAnalyzer.java
index 40d61d2..0c40896 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/profile/analyze/ProfileAnalyzer.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/profile/analyze/ProfileAnalyzer.java
@@ -96,7 +96,7 @@ public class ProfileAnalyzer {
         return analyzation;
     }
 
-    protected SequenceSearch getAllSequenceRange(String segmentId, List<ProfileAnalyzeTimeRange> timeRanges) throws IOException {
+    protected SequenceSearch getAllSequenceRange(String segmentId, List<ProfileAnalyzeTimeRange> timeRanges) {
         final List<SequenceSearch> searches = timeRanges.parallelStream().map(r -> {
             try {
                 return getAllSequenceRange(segmentId, r.getStart(), r.getEnd());
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/AggregationQueryService.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/AggregationQueryService.java
index ffa7dae..b815ef3 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/AggregationQueryService.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/AggregationQueryService.java
@@ -70,7 +70,7 @@ public class AggregationQueryService implements Service {
                 case ServiceInstance:
                     final IDManager.ServiceInstanceID.InstanceIDDefinition instanceIDDefinition
                         = IDManager.ServiceInstanceID.analysisId(selectedRecord.getId());
-                    /**
+                    /*
                      * Add the service name into the name if this is global top N.
                      */
                     if (StringUtil.isEmpty(condition.getParentService())) {
@@ -84,7 +84,7 @@ public class AggregationQueryService implements Service {
                 case Endpoint:
                     final IDManager.EndpointID.EndpointIDDefinition endpointIDDefinition
                         = IDManager.EndpointID.analysisId(selectedRecord.getId());
-                    /**
+                    /*
                      * Add the service name into the name if this is global top N.
                      */
                     if (StringUtil.isEmpty(condition.getParentService())) {
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/ttl/DataTTLKeeperTimer.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/ttl/DataTTLKeeperTimer.java
index 53f565a..ad71b7b 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/ttl/DataTTLKeeperTimer.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/ttl/DataTTLKeeperTimer.java
@@ -98,6 +98,13 @@ public enum DataTTLKeeperTimer {
             if (!model.isTimeSeries()) {
                 return;
             }
+            if (log.isDebugEnabled()) {
+                log.debug(
+                    "Is record? {}. RecordDataTTL {}, MetricsDataTTL {}",
+                    model.isRecord(),
+                    moduleConfig.getRecordDataTTL(),
+                    moduleConfig.getMetricsDataTTL());
+            }
             moduleManager.find(StorageModule.NAME)
                          .provider()
                          .getService(IHistoryDeleteDAO.class)
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/type/StorageDataComplexObject.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/type/StorageDataComplexObject.java
index ebad19e..7a8d4a5 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/type/StorageDataComplexObject.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/type/StorageDataComplexObject.java
@@ -18,9 +18,16 @@
 
 package org.apache.skywalking.oap.server.core.storage.type;
 
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import java.io.IOException;
+
 /**
  * StorageDataComplexObject implementation supports String-Object interconversion.
  */
+@JsonSerialize(using = StorageDataComplexObject.Serializer.class)
 public interface StorageDataComplexObject<T> {
     /**
      * @return string representing this object.
@@ -36,4 +43,15 @@ public interface StorageDataComplexObject<T> {
      * Initialize the object based on the given source.
      */
     void copyFrom(T source);
+
+    final class Serializer extends JsonSerializer<StorageDataComplexObject<?>> {
+        @Override
+        public void serialize(
+            final StorageDataComplexObject value,
+            final JsonGenerator gen,
+            final SerializerProvider provider)
+            throws IOException {
+            gen.writeString(value.toStorageData());
+        }
+    }
 }
diff --git a/oap-server/server-core/src/test/java/org/apache/skywalking/oap/server/core/remote/JettyServerTest.java b/oap-server/server-core/src/test/java/org/apache/skywalking/oap/server/core/remote/JettyServerTest.java
index 64d8132..be93963 100644
--- a/oap-server/server-core/src/test/java/org/apache/skywalking/oap/server/core/remote/JettyServerTest.java
+++ b/oap-server/server-core/src/test/java/org/apache/skywalking/oap/server/core/remote/JettyServerTest.java
@@ -18,20 +18,9 @@
 
 package org.apache.skywalking.oap.server.core.remote;
 
-import java.io.IOException;
-import javax.servlet.ServletException;
+import com.linecorp.armeria.client.WebClient;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
-import org.apache.http.client.methods.CloseableHttpResponse;
-import org.apache.http.client.methods.HttpDelete;
-import org.apache.http.client.methods.HttpGet;
-import org.apache.http.client.methods.HttpHead;
-import org.apache.http.client.methods.HttpOptions;
-import org.apache.http.client.methods.HttpPost;
-import org.apache.http.client.methods.HttpPut;
-import org.apache.http.client.methods.HttpTrace;
-import org.apache.http.impl.client.CloseableHttpClient;
-import org.apache.http.impl.client.HttpClientBuilder;
 import org.apache.skywalking.oap.server.library.server.jetty.JettyHandler;
 import org.apache.skywalking.oap.server.library.server.jetty.JettyServer;
 import org.apache.skywalking.oap.server.library.server.jetty.JettyServerConfig;
@@ -66,88 +55,62 @@ public class JettyServerTest {
     }
 
     @Test
-    public void test() {
+    public void test() throws Exception {
 
         String rootURI = "http://localhost:12800";
         String testHandlerURI = "http://localhost:12800/test";
         String testNoHandlerURI = "http://localhost:12800/test/noHandler";
-        CloseableHttpClient httpClient = HttpClientBuilder.create().build();
-        HttpGet httpGet = new HttpGet(rootURI);
-        HttpPost httpPost = new HttpPost(rootURI);
-        HttpPost httpPostTestHandler = new HttpPost(testHandlerURI);
-        HttpPost httpPostTestNoHandler = new HttpPost(testNoHandlerURI);
-        HttpTrace httpTrace = new HttpTrace(testHandlerURI);
-        HttpTrace httpTraceRoot = new HttpTrace(rootURI);
-        HttpPut httpPut = new HttpPut(testHandlerURI);
-        HttpDelete httpDelete = new HttpDelete(testHandlerURI);
-        HttpOptions httpOptions = new HttpOptions(testHandlerURI);
-        HttpHead httpHead = new HttpHead(testHandlerURI);
-        CloseableHttpResponse response = null;
-        try {
-            //get
-            response = httpClient.execute(httpGet);
-            Assert.assertEquals(response.getStatusLine().getStatusCode(), 405);
-            response.close();
-
-            //post root
-            response = httpClient.execute(httpPost);
-            Assert.assertEquals(response.getStatusLine().getStatusCode(), 404);
-            response.close();
-
-            //post
-            response = httpClient.execute(httpPostTestHandler);
-            Assert.assertEquals(response.getStatusLine().getStatusCode(), 200);
-            response.close();
-
-            //post no handler
-            response = httpClient.execute(httpPostTestNoHandler);
-            Assert.assertEquals(response.getStatusLine().getStatusCode(), 404);
-            response.close();
-
-            //trace
-            response = httpClient.execute(httpTrace);
-            Assert.assertEquals(response.getStatusLine().getStatusCode(), 405);
-            response.close();
-
-            //trace root
-            response = httpClient.execute(httpTraceRoot);
-            Assert.assertEquals(response.getStatusLine().getStatusCode(), 405);
-            response.close();
-
-            //put
-            response = httpClient.execute(httpPut);
-            Assert.assertEquals(response.getStatusLine().getStatusCode(), 405);
-            response.close();
-
-            //delete
-            response = httpClient.execute(httpDelete);
-            Assert.assertEquals(response.getStatusLine().getStatusCode(), 405);
-            response.close();
-
-            //options
-            response = httpClient.execute(httpOptions);
-            Assert.assertEquals(response.getStatusLine().getStatusCode(), 405);
-            response.close();
-
-            //head
-            response = httpClient.execute(httpHead);
-            Assert.assertEquals(response.getStatusLine().getStatusCode(), 405);
-            response.close();
-        } catch (IOException e) {
-            Assert.fail("Test failed!");
-            e.printStackTrace();
-        } finally {
-            try {
-                if (httpClient != null) {
-                    httpClient.close();
-                }
-                if (response != null) {
-                    response.close();
-                }
-            } catch (IOException e) {
-                e.printStackTrace();
-            }
-        }
+
+        Assert.assertEquals(
+            WebClient.of().get(rootURI).aggregate().get().status().code(),
+            405
+        );
+
+        Assert.assertEquals(
+            WebClient.of().post(rootURI, new byte[0]).aggregate().get().status().code(),
+            404
+        );
+
+        Assert.assertEquals(
+            WebClient.of().post(testHandlerURI, new byte[0]).aggregate().get().status().code(),
+            200
+        );
+
+        Assert.assertEquals(
+            WebClient.of().post(testNoHandlerURI, new byte[0]).aggregate().get().status()
+                     .code(),
+            404
+        );
+
+        Assert.assertEquals(
+            WebClient.of().trace(testNoHandlerURI).aggregate().get().status().code(),
+            405
+        );
+
+        Assert.assertEquals(
+            WebClient.of().trace(rootURI).aggregate().get().status().code(),
+            405
+        );
+
+        Assert.assertEquals(
+            WebClient.of().put(testHandlerURI, new byte[0]).aggregate().get().status().code(),
+            405
+        );
+
+        Assert.assertEquals(
+            WebClient.of().delete(testHandlerURI).aggregate().get().status().code(),
+            405
+        );
+
+        Assert.assertEquals(
+            WebClient.of().options(testHandlerURI).aggregate().get().status().code(),
+            405
+        );
+
+        Assert.assertEquals(
+            WebClient.of().head(testHandlerURI).aggregate().get().status().code(),
+            405
+        );
     }
 
     static class TestPostHandler extends JettyHandler {
@@ -159,7 +122,7 @@ public class JettyServerTest {
 
         @Override
         protected void doPost(final HttpServletRequest req,
-                              final HttpServletResponse resp) throws ServletException, IOException {
+                              final HttpServletResponse resp) {
             resp.setStatus(HttpServletResponse.SC_OK);
         }
 
diff --git a/oap-server/server-library/library-client/pom.xml b/oap-server/server-library/library-client/pom.xml
index d5c6b82..5bfef63 100755
--- a/oap-server/server-library/library-client/pom.xml
+++ b/oap-server/server-library/library-client/pom.xml
@@ -40,6 +40,12 @@
         </dependency>
 
         <dependency>
+            <groupId>org.apache.skywalking</groupId>
+            <artifactId>library-elasticsearch-client</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
             <groupId>io.grpc</groupId>
             <artifactId>grpc-core</artifactId>
         </dependency>
@@ -72,110 +78,14 @@
             <artifactId>commons-dbcp</artifactId>
         </dependency>
         <dependency>
-            <groupId>org.elasticsearch.client</groupId>
-            <artifactId>elasticsearch-rest-high-level-client</artifactId>
-        </dependency>
-        <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>jcl-over-slf4j</artifactId>
         </dependency>
-    </dependencies>
 
-    <profiles>
-        <profile>
-            <id>CI-with-IT</id>
-            <build>
-                <plugins>
-                    <plugin>
-                        <groupId>io.fabric8</groupId>
-                        <artifactId>docker-maven-plugin</artifactId>
-                        <configuration>
-                            <sourceMode>all</sourceMode>
-                            <logDate>default</logDate>
-                            <verbose>true</verbose>
-                            <imagePullPolicy>IfNotPresent</imagePullPolicy>
-                        </configuration>
-                        <executions>
-                            <execution>
-                                <id>prepare-elasticsearch</id>
-                                <phase>pre-integration-test</phase>
-                                <goals>
-                                    <goal>start</goal>
-                                </goals>
-                                <configuration>
-                                    <images>
-                                        <image>
-                                            <name>elastic/elasticsearch:${test.elasticsearch.version}</name>
-                                            <alias>elastic-client-integration-test</alias>
-                                            <run>
-                                                <ports>
-                                                    <port>es-port:9200</port>
-                                                </ports>
-                                                <wait>
-                                                    <log>started</log>
-                                                    <time>60000</time>
-                                                </wait>
-                                                <env>
-                                                    <discovery.type>single-node</discovery.type>
-                                                </env>
-                                            </run>
-                                        </image>
-                                    </images>
-                                </configuration>
-                            </execution>
-                            <execution>
-                                <id>remove-it-database</id>
-                                <phase>post-integration-test</phase>
-                                <goals>
-                                    <goal>stop</goal>
-                                </goals>
-                            </execution>
-                        </executions>
-                    </plugin>
-                    <plugin>
-                        <groupId>org.codehaus.gmaven</groupId>
-                        <artifactId>gmaven-plugin</artifactId>
-                        <version>${gmaven-plugin.version}</version>
-                        <executions>
-                            <execution>
-                                <id>add-default-properties</id>
-                                <phase>initialize</phase>
-                                <goals>
-                                    <goal>execute</goal>
-                                </goals>
-                                <configuration>
-                                    <providerSelection>2.0</providerSelection>
-                                    <source>
-                                        project.properties.setProperty('docker.hostname', 'localhost')
-                                    </source>
-                                </configuration>
-                            </execution>
-                        </executions>
-                    </plugin>
-                    <plugin>
-                        <groupId>org.apache.maven.plugins</groupId>
-                        <artifactId>maven-failsafe-plugin</artifactId>
-                        <configuration>
-                            <systemPropertyVariables>
-                                <elastic.search.address>
-                                    ${docker.hostname}:${es-port}
-                                </elastic.search.address>
-                                <elastic.search.protocol>
-                                    http
-                                </elastic.search.protocol>
-                            </systemPropertyVariables>
-                        </configuration>
-                        <executions>
-                            <execution>
-                                <goals>
-                                    <goal>integration-test</goal>
-                                    <goal>verify</goal>
-                                </goals>
-                            </execution>
-                        </executions>
-                    </plugin>
-                </plugins>
-            </build>
-        </profile>
-    </profiles>
+        <dependency>
+            <groupId>org.testcontainers</groupId>
+            <artifactId>elasticsearch</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
 </project>
diff --git a/oap-server/server-library/library-client/src/main/java/org/apache/skywalking/oap/server/library/client/elasticsearch/ElasticSearchClient.java b/oap-server/server-library/library-client/src/main/java/org/apache/skywalking/oap/server/library/client/elasticsearch/ElasticSearchClient.java
index 38e3af6..912c235 100644
--- a/oap-server/server-library/library-client/src/main/java/org/apache/skywalking/oap/server/library/client/elasticsearch/ElasticSearchClient.java
+++ b/oap-server/server-library/library-client/src/main/java/org/apache/skywalking/oap/server/library/client/elasticsearch/ElasticSearchClient.java
@@ -18,86 +18,36 @@
 
 package org.apache.skywalking.oap.server.library.client.elasticsearch;
 
-import com.google.common.base.Splitter;
-import com.google.gson.Gson;
-import com.google.gson.JsonObject;
-import com.google.gson.reflect.TypeToken;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.lang.reflect.Type;
-import java.nio.file.Files;
-import java.nio.file.Paths;
-import java.security.KeyManagementException;
-import java.security.KeyStore;
-import java.security.KeyStoreException;
-import java.security.NoSuchAlgorithmException;
-import java.security.cert.CertificateException;
-import java.util.ArrayList;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableMap;
+import java.time.Duration;
 import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
+import java.util.Collection;
 import java.util.Map;
 import java.util.Optional;
-import java.util.concurrent.locks.ReentrantLock;
-import javax.net.ssl.SSLContext;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Function;
+import java.util.function.Supplier;
 import lombok.RequiredArgsConstructor;
 import lombok.Setter;
 import lombok.extern.slf4j.Slf4j;
-import org.apache.http.HttpEntity;
-import org.apache.http.HttpHost;
-import org.apache.http.HttpStatus;
-import org.apache.http.auth.AuthScope;
-import org.apache.http.auth.UsernamePasswordCredentials;
-import org.apache.http.client.CredentialsProvider;
-import org.apache.http.client.methods.HttpDelete;
-import org.apache.http.client.methods.HttpGet;
-import org.apache.http.client.methods.HttpHead;
-import org.apache.http.client.methods.HttpPost;
-import org.apache.http.client.methods.HttpPut;
-import org.apache.http.entity.ContentType;
-import org.apache.http.impl.client.BasicCredentialsProvider;
-import org.apache.http.nio.entity.NStringEntity;
-import org.apache.http.ssl.SSLContextBuilder;
-import org.apache.http.ssl.SSLContexts;
 import org.apache.skywalking.apm.util.StringUtil;
+import org.apache.skywalking.library.elasticsearch.ElasticSearch;
+import org.apache.skywalking.library.elasticsearch.ElasticSearchBuilder;
+import org.apache.skywalking.library.elasticsearch.ElasticSearchVersion;
+import org.apache.skywalking.library.elasticsearch.bulk.BulkProcessor;
+import org.apache.skywalking.library.elasticsearch.requests.search.Search;
+import org.apache.skywalking.library.elasticsearch.response.Document;
+import org.apache.skywalking.library.elasticsearch.response.Documents;
+import org.apache.skywalking.library.elasticsearch.response.Index;
+import org.apache.skywalking.library.elasticsearch.response.IndexTemplate;
+import org.apache.skywalking.library.elasticsearch.response.Mappings;
+import org.apache.skywalking.library.elasticsearch.response.search.SearchResponse;
 import org.apache.skywalking.oap.server.library.client.Client;
 import org.apache.skywalking.oap.server.library.client.healthcheck.DelegatedHealthChecker;
 import org.apache.skywalking.oap.server.library.client.healthcheck.HealthCheckable;
-import org.apache.skywalking.oap.server.library.client.request.InsertRequest;
-import org.apache.skywalking.oap.server.library.client.request.UpdateRequest;
 import org.apache.skywalking.oap.server.library.util.HealthChecker;
-import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
-import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;
-import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
-import org.elasticsearch.action.admin.indices.delete.DeleteIndexResponse;
-import org.elasticsearch.action.admin.indices.get.GetIndexRequest;
-import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest;
-import org.elasticsearch.action.admin.indices.mapping.put.PutMappingResponse;
-import org.elasticsearch.action.bulk.BackoffPolicy;
-import org.elasticsearch.action.bulk.BulkProcessor;
-import org.elasticsearch.action.bulk.BulkRequest;
-import org.elasticsearch.action.bulk.BulkResponse;
-import org.elasticsearch.action.get.GetRequest;
-import org.elasticsearch.action.get.GetResponse;
-import org.elasticsearch.action.index.IndexRequest;
-import org.elasticsearch.action.search.SearchRequest;
-import org.elasticsearch.action.search.SearchResponse;
-import org.elasticsearch.action.support.ActiveShardCount;
-import org.elasticsearch.action.support.IndicesOptions;
-import org.elasticsearch.action.support.WriteRequest;
-import org.elasticsearch.client.Response;
-import org.elasticsearch.client.ResponseException;
-import org.elasticsearch.client.RestClient;
-import org.elasticsearch.client.RestClientBuilder;
-import org.elasticsearch.client.RestHighLevelClient;
-import org.elasticsearch.common.unit.TimeValue;
-import org.elasticsearch.common.xcontent.XContentBuilder;
-import org.elasticsearch.common.xcontent.XContentType;
-import org.elasticsearch.index.query.QueryBuilders;
-import org.elasticsearch.search.builder.SearchSourceBuilder;
 
 /**
  * ElasticSearchClient connects to the ES server by using ES client APIs.
@@ -106,516 +56,265 @@ import org.elasticsearch.search.builder.SearchSourceBuilder;
 @RequiredArgsConstructor
 public class ElasticSearchClient implements Client, HealthCheckable {
     public static final String TYPE = "type";
-    protected final String clusterNodes;
-    protected final String protocol;
+
+    private final String clusterNodes;
+
+    private final String protocol;
+
     private final String trustStorePath;
+
     @Setter
     private volatile String trustStorePass;
+
     @Setter
     private volatile String user;
+
     @Setter
     private volatile String password;
-    private final List<IndexNameConverter> indexNameConverters;
-    protected volatile RestHighLevelClient client;
-    protected DelegatedHealthChecker healthChecker = new DelegatedHealthChecker();
-    protected final ReentrantLock connectLock = new ReentrantLock();
+
+    private final Function<String, String> indexNameConverter;
+
+    private final DelegatedHealthChecker healthChecker = new DelegatedHealthChecker();
+
     private final int connectTimeout;
+
     private final int socketTimeout;
 
+    private final int numHttpClientThread;
+
+    private final AtomicReference<ElasticSearch> es = new AtomicReference<>();
+
     public ElasticSearchClient(String clusterNodes,
                                String protocol,
                                String trustStorePath,
                                String trustStorePass,
                                String user,
                                String password,
-                               List<IndexNameConverter> indexNameConverters,
+                               Function<String, String> indexNameConverter,
                                int connectTimeout,
-                               int socketTimeout) {
+                               int socketTimeout,
+                               int numHttpClientThread) {
         this.clusterNodes = clusterNodes;
         this.protocol = protocol;
-        this.user = user;
-        this.password = password;
-        this.indexNameConverters = indexNameConverters;
         this.trustStorePath = trustStorePath;
         this.trustStorePass = trustStorePass;
+        this.user = user;
+        this.password = password;
+        this.indexNameConverter = indexNameConverter;
         this.connectTimeout = connectTimeout;
         this.socketTimeout = socketTimeout;
+        this.numHttpClientThread = numHttpClientThread;
     }
 
     @Override
-    public void connect() throws IOException, KeyStoreException, NoSuchAlgorithmException, KeyManagementException, CertificateException {
-        connectLock.lock();
-        try {
-            List<HttpHost> hosts = parseClusterNodes(protocol, clusterNodes);
-            if (client != null) {
-                try {
-                    client.close();
-                } catch (Throwable t) {
-                    log.error("ElasticSearch client reconnection fails based on new config", t);
-                }
-            }
-            client = createClient(hosts);
-            client.ping();
-        } finally {
-            connectLock.unlock();
+    public void connect() {
+        final ElasticSearch oldOne = es.get();
+
+        final ElasticSearchBuilder cb =
+            ElasticSearch
+                .builder()
+                .endpoints(clusterNodes.split(","))
+                .protocol(protocol)
+                .connectTimeout(connectTimeout)
+                .numHttpClientThread(numHttpClientThread)
+                .healthyListener(healthy -> {
+                    if (healthy) {
+                        healthChecker.health();
+                    } else {
+                        healthChecker.unHealth("No healthy endpoint");
+                    }
+                });
+
+        if (!Strings.isNullOrEmpty(trustStorePath)) {
+            cb.trustStorePath(trustStorePath);
+        }
+        if (!Strings.isNullOrEmpty(trustStorePass)) {
+            cb.trustStorePass(trustStorePass);
+        }
+        if (!Strings.isNullOrEmpty(user)) {
+            cb.username(user);
+        }
+        if (!Strings.isNullOrEmpty(password)) {
+            cb.password(password);
         }
-    }
 
-    protected RestHighLevelClient createClient(
-        final List<HttpHost> pairsList) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, KeyManagementException {
-        RestClientBuilder builder;
-        if (StringUtil.isNotEmpty(user) && StringUtil.isNotEmpty(password)) {
-            final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
-            credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(user, password));
-
-            if (StringUtil.isEmpty(trustStorePath)) {
-                builder = RestClient.builder(pairsList.toArray(new HttpHost[0]))
-                                    .setHttpClientConfigCallback(
-                                        httpClientBuilder -> httpClientBuilder.setDefaultCredentialsProvider(
-                                            credentialsProvider));
+        final ElasticSearch newOne = cb.build();
+        // Only swap the old / new after the new one established a new connection.
+        final CompletableFuture<ElasticSearchVersion> f = newOne.connect();
+        f.whenComplete((ignored, exception) -> {
+            if (exception != null) {
+                log.error("Failed to recreate ElasticSearch client based on config", exception);
+                return;
+            }
+            if (es.compareAndSet(oldOne, newOne)) {
+                oldOne.close();
             } else {
-                KeyStore truststore = KeyStore.getInstance("jks");
-                try (InputStream is = Files.newInputStream(Paths.get(trustStorePath))) {
-                    truststore.load(is, trustStorePass.toCharArray());
-                }
-                SSLContextBuilder sslBuilder = SSLContexts.custom().loadTrustMaterial(truststore, null);
-                final SSLContext sslContext = sslBuilder.build();
-                builder = RestClient.builder(pairsList.toArray(new HttpHost[0]))
-                                    .setHttpClientConfigCallback(
-                                        httpClientBuilder -> httpClientBuilder.setDefaultCredentialsProvider(
-                                            credentialsProvider)
-                                                                              .setSSLContext(sslContext));
+                newOne.close();
             }
-        } else {
-            builder = RestClient.builder(pairsList.toArray(new HttpHost[0]));
-        }
-        builder.setRequestConfigCallback(
-            requestConfigBuilder -> requestConfigBuilder
-                .setConnectTimeout(connectTimeout)
-                .setSocketTimeout(socketTimeout)
-        );
-
-        return new RestHighLevelClient(builder);
+        });
+        f.join();
     }
 
     @Override
-    public void shutdown() throws IOException {
-        client.close();
+    public void shutdown() {
+        es.get().close();
     }
 
-    public static List<HttpHost> parseClusterNodes(String protocol, String nodes) {
-        List<HttpHost> httpHosts = new LinkedList<>();
-        log.info("elasticsearch cluster nodes: {}", nodes);
-        List<String> nodesSplit = Splitter.on(",").omitEmptyStrings().splitToList(nodes);
-
-        for (String node : nodesSplit) {
-            String host = node.split(":")[0];
-            String port = node.split(":")[1];
-            httpHosts.add(new HttpHost(host, Integer.parseInt(port), protocol));
-        }
+    @Override
+    public void registerChecker(HealthChecker healthChecker) {
+        this.healthChecker.register(healthChecker);
+    }
 
-        return httpHosts;
+    public boolean createIndex(String indexName) {
+        return createIndex(indexName, null, null);
     }
 
-    public boolean createIndex(String indexName) throws IOException {
-        indexName = formatIndexName(indexName);
+    public boolean createIndex(String indexName,
+                               Mappings mappings,
+                               Map<String, ?> settings) {
+        indexName = indexNameConverter.apply(indexName);
 
-        CreateIndexRequest request = new CreateIndexRequest(indexName);
-        CreateIndexResponse response = client.indices().create(request);
-        log.debug("create {} index finished, isAcknowledged: {}", indexName, response.isAcknowledged());
-        return response.isAcknowledged();
+        return es.get().index().create(indexName, mappings, settings);
     }
 
-    public boolean updateIndexMapping(String indexName, Map<String, Object> mapping) throws IOException {
-        indexName = formatIndexName(indexName);
-        Map<String, Object> properties = (Map<String, Object>) mapping.get(ElasticSearchClient.TYPE);
-        PutMappingRequest putMappingRequest = new PutMappingRequest(indexName);
-        Gson gson = new Gson();
-        putMappingRequest.type(ElasticSearchClient.TYPE);
-        putMappingRequest.source(gson.toJson(properties), XContentType.JSON);
-        PutMappingResponse response = client.indices().putMapping(putMappingRequest);
-        log.debug("put {} index mapping finished, isAcknowledged: {}", indexName, response.isAcknowledged());
-        return response.isAcknowledged();
+    public boolean updateIndexMapping(String indexName, Mappings mapping) {
+        indexName = indexNameConverter.apply(indexName);
+
+        return es.get().index().putMapping(indexName, TYPE, mapping);
     }
 
-    public Map<String, Object> getIndex(String indexName) throws IOException {
+    public Optional<Index> getIndex(String indexName) {
         if (StringUtil.isBlank(indexName)) {
-            return new HashMap<>();
-        }
-        indexName = formatIndexName(indexName);
-        try {
-            Response response = client.getLowLevelClient()
-                                      .performRequest(HttpGet.METHOD_NAME, "/" + indexName);
-            int statusCode = response.getStatusLine().getStatusCode();
-            if (statusCode != HttpStatus.SC_OK) {
-                healthChecker.health();
-                throw new IOException(
-                    "The response status code of template exists request should be 200, but it is " + statusCode);
-            }
-            Type type = new TypeToken<HashMap<String, Object>>() {
-            }.getType();
-            Map<String, Object> templates = new Gson().<HashMap<String, Object>>fromJson(
-                new InputStreamReader(response.getEntity().getContent()),
-                type
-            );
-            return (Map<String, Object>) Optional.ofNullable(templates.get(indexName)).orElse(new HashMap<>());
-        } catch (ResponseException e) {
-            if (e.getResponse().getStatusLine().getStatusCode() == HttpStatus.SC_NOT_FOUND) {
-                return new HashMap<>();
-            }
-            healthChecker.unHealth(e);
-            throw e;
-        } catch (IOException t) {
-            healthChecker.unHealth(t);
-            throw t;
+            return Optional.empty();
         }
+        indexName = indexNameConverter.apply(indexName);
+        return es.get().index().get(indexName);
     }
 
-    public boolean createIndex(String indexName, Map<String, Object> settings,
-                               Map<String, Object> mapping) throws IOException {
-        indexName = formatIndexName(indexName);
-        CreateIndexRequest request = new CreateIndexRequest(indexName);
-        Gson gson = new Gson();
-        request.settings(gson.toJson(settings), XContentType.JSON);
-        request.mapping(TYPE, gson.toJson(mapping), XContentType.JSON);
-        CreateIndexResponse response = client.indices().create(request);
-        log.debug("create {} index finished, isAcknowledged: {}", indexName, response.isAcknowledged());
-        return response.isAcknowledged();
-    }
+    public Collection<String> retrievalIndexByAliases(String alias) {
+        alias = indexNameConverter.apply(alias);
 
-    public List<String> retrievalIndexByAliases(String aliases) throws IOException {
-        aliases = formatIndexName(aliases);
-        Response response;
-        try {
-            response = client.getLowLevelClient().performRequest(HttpGet.METHOD_NAME, "/_alias/" + aliases);
-            healthChecker.health();
-        } catch (Throwable t) {
-            healthChecker.unHealth(t);
-            throw t;
-        }
-        if (HttpStatus.SC_OK == response.getStatusLine().getStatusCode()) {
-            Gson gson = new Gson();
-            InputStreamReader reader;
-            try {
-                reader = new InputStreamReader(response.getEntity().getContent());
-            } catch (Throwable t) {
-                healthChecker.unHealth(t);
-                throw t;
-            }
-            JsonObject responseJson = gson.fromJson(reader, JsonObject.class);
-            log.debug("retrieval indexes by aliases {}, response is {}", aliases, responseJson);
-            return new ArrayList<>(responseJson.keySet());
-        }
-        return Collections.emptyList();
+        return es.get().alias().indices(alias).keySet();
     }
 
     /**
-     * If your indexName is retrieved from elasticsearch through {@link #retrievalIndexByAliases(String)} or some other
-     * method and it already contains namespace. Then you should delete the index by this method, this method will no
-     * longer concatenate namespace.
-     * <p>
+     * If your indexName is retrieved from elasticsearch through {@link
+     * #retrievalIndexByAliases(String)} or some other method and it already contains namespace.
+     * Then you should delete the index by this method, this method will no longer concatenate
+     * namespace.
+     *
      * https://github.com/apache/skywalking/pull/3017
      */
-    public boolean deleteByIndexName(String indexName) throws IOException {
-        return deleteIndex(indexName, false);
+    public boolean deleteByIndexName(String indexName) {
+        return es.get().index().delete(indexName);
     }
 
-    /**
-     * If your indexName is obtained from metadata or configuration and without namespace. Then you should delete the
-     * index by this method, this method automatically concatenates namespace.
-     * <p>
-     * https://github.com/apache/skywalking/pull/3017
-     */
-    public boolean deleteByModelName(String modelName) throws IOException {
-        return deleteIndex(modelName, true);
-    }
+    public boolean isExistsIndex(String indexName) {
+        indexName = indexNameConverter.apply(indexName);
 
-    protected boolean deleteIndex(String indexName, boolean formatIndexName) throws IOException {
-        if (formatIndexName) {
-            indexName = formatIndexName(indexName);
-        }
-        DeleteIndexRequest request = new DeleteIndexRequest(indexName);
-        DeleteIndexResponse response;
-        response = client.indices().delete(request);
-        log.debug("delete {} index finished, isAcknowledged: {}", indexName, response.isAcknowledged());
-        return response.isAcknowledged();
+        return es.get().index().exists(indexName);
     }
 
-    public boolean isExistsIndex(String indexName) throws IOException {
-        indexName = formatIndexName(indexName);
-        GetIndexRequest request = new GetIndexRequest();
-        request.indices(indexName);
-        return client.indices().exists(request);
-    }
+    public Optional<IndexTemplate> getTemplate(String name) {
+        name = indexNameConverter.apply(name);
 
-    public Map<String, Object> getTemplate(String name) throws IOException {
-        name = formatIndexName(name);
-        try {
-            Response response = client.getLowLevelClient()
-                                      .performRequest(HttpGet.METHOD_NAME, "/_template/" + name);
-            int statusCode = response.getStatusLine().getStatusCode();
-            if (statusCode != HttpStatus.SC_OK) {
-                healthChecker.health();
-                throw new IOException(
-                    "The response status code of template exists request should be 200, but it is " + statusCode);
-            }
-            Type type = new TypeToken<HashMap<String, Object>>() {
-            }.getType();
-            Map<String, Object> templates = new Gson().<HashMap<String, Object>>fromJson(
-                new InputStreamReader(response.getEntity().getContent()),
-                type
-            );
-            if (templates.containsKey(name)) {
-                return (Map<String, Object>) templates.get(name);
-            }
-            return new HashMap<>();
-        } catch (ResponseException e) {
-            if (e.getResponse().getStatusLine().getStatusCode() == HttpStatus.SC_NOT_FOUND) {
-                return new HashMap<>();
-            }
-            healthChecker.unHealth(e);
-            throw e;
-        } catch (IOException t) {
-            healthChecker.unHealth(t);
-            throw t;
-        }
+        return es.get().templates().get(name);
     }
 
-    public boolean isExistsTemplate(String indexName) throws IOException {
-        indexName = formatIndexName(indexName);
-
-        Response response = client.getLowLevelClient().performRequest(HttpHead.METHOD_NAME, "/_template/" + indexName);
+    public boolean isExistsTemplate(String indexName) {
+        indexName = indexNameConverter.apply(indexName);
 
-        int statusCode = response.getStatusLine().getStatusCode();
-        if (statusCode == HttpStatus.SC_OK) {
-            return true;
-        } else if (statusCode == HttpStatus.SC_NOT_FOUND) {
-            return false;
-        } else {
-            throw new IOException(
-                "The response status code of template exists request should be 200 or 404, but it is " + statusCode);
-        }
+        return es.get().templates().exists(indexName);
     }
 
     public boolean createOrUpdateTemplate(String indexName, Map<String, Object> settings,
-                                          Map<String, Object> mapping, int order) throws IOException {
-        indexName = formatIndexName(indexName);
-
-        String[] patterns = new String[] {indexName + "-*"};
-
-        Map<String, Object> aliases = new HashMap<>();
-        aliases.put(indexName, new JsonObject());
+                                          Mappings mapping, int order) {
+        indexName = indexNameConverter.apply(indexName);
 
-        Map<String, Object> template = new HashMap<>();
-        template.put("index_patterns", patterns);
-        template.put("aliases", aliases);
-        template.put("settings", settings);
-        template.put("mappings", mapping);
-        template.put("order", order);
-
-        HttpEntity entity = new NStringEntity(new Gson().toJson(template), ContentType.APPLICATION_JSON);
-
-        Response response = client.getLowLevelClient()
-                                  .performRequest(
-                                      HttpPut.METHOD_NAME, "/_template/" + indexName, Collections.emptyMap(), entity);
-        return response.getStatusLine().getStatusCode() == HttpStatus.SC_OK;
+        return es.get().templates().createOrUpdate(indexName, settings, mapping, order);
     }
 
-    public boolean deleteTemplate(String indexName) throws IOException {
-        indexName = formatIndexName(indexName);
-        Response response = client.getLowLevelClient()
-                                  .performRequest(HttpDelete.METHOD_NAME, "/_template/" + indexName);
-        return response.getStatusLine().getStatusCode() == HttpStatus.SC_OK;
-    }
+    public boolean deleteTemplate(String indexName) {
+        indexName = indexNameConverter.apply(indexName);
 
-    public SearchResponse search(IndexNameMaker indexNameMaker,
-                                 SearchSourceBuilder searchSourceBuilder) throws IOException {
-        String[] indexNames = Arrays.stream(indexNameMaker.make()).map(this::formatIndexName).toArray(String[]::new);
-        return doSearch(searchSourceBuilder, indexNames);
+        return es.get().templates().delete(indexName);
     }
 
-    public SearchResponse search(String indexName, SearchSourceBuilder searchSourceBuilder) throws IOException {
-        indexName = formatIndexName(indexName);
-        return doSearch(searchSourceBuilder, indexName);
+    public SearchResponse search(Supplier<String[]> indices, Search search) {
+        final String[] indexNames =
+            Arrays.stream(indices.get())
+                  .map(indexNameConverter)
+                  .toArray(String[]::new);
+        return es.get().search(
+            search,
+            ImmutableMap.of(
+                "ignore_unavailable", true,
+                "allow_no_indices", true,
+                "expand_wildcards", "open"
+            ),
+            indexNames
+        );
     }
 
-    protected SearchResponse doSearch(SearchSourceBuilder searchSourceBuilder,
-                                      String... indexNames) throws IOException {
-        SearchRequest searchRequest = new SearchRequest(indexNames);
-        searchRequest.indicesOptions(IndicesOptions.fromOptions(true, true, true, false));
-        searchRequest.types(TYPE);
-        searchRequest.source(searchSourceBuilder);
-        try {
-            SearchResponse response = client.search(searchRequest);
-            healthChecker.health();
-            return response;
-        } catch (Throwable t) {
-            healthChecker.unHealth(t);
-            handleIOPoolStopped(t);
-            throw t;
-        }
-    }
+    public SearchResponse search(String indexName, Search search) {
+        indexName = indexNameConverter.apply(indexName);
 
-    protected void handleIOPoolStopped(Throwable t) throws IOException {
-        if (!(t instanceof IllegalStateException)) {
-            return;
-        }
-        IllegalStateException ise = (IllegalStateException) t;
-        // Fixed the issue described in https://github.com/elastic/elasticsearch/issues/39946
-        if (ise.getMessage().contains("I/O reactor status: STOPPED") &&
-            connectLock.tryLock()) {
-            try {
-                connect();
-            } catch (KeyStoreException | NoSuchAlgorithmException | KeyManagementException | CertificateException e) {
-                throw new IllegalStateException("Can't reconnect to Elasticsearch", e);
-            }
-        }
+        return es.get().search(search, indexName);
     }
 
-    public GetResponse get(String indexName, String id) throws IOException {
-        indexName = formatIndexName(indexName);
-        GetRequest request = new GetRequest(indexName, TYPE, id);
-        try {
-            GetResponse response = client.get(request);
-            healthChecker.health();
-            return response;
-        } catch (Throwable t) {
-            healthChecker.unHealth(t);
-            throw t;
-        }
-    }
+    public Optional<Document> get(String indexName, String id) {
+        indexName = indexNameConverter.apply(indexName);
 
-    public SearchResponse ids(String indexName, String[] ids) throws IOException {
-        indexName = formatIndexName(indexName);
-
-        SearchRequest searchRequest = new SearchRequest(indexName);
-        searchRequest.types(TYPE);
-        searchRequest.source().query(QueryBuilders.idsQuery().addIds(ids)).size(ids.length);
-        try {
-            SearchResponse response = client.search(searchRequest);
-            healthChecker.health();
-            return response;
-        } catch (Throwable t) {
-            healthChecker.unHealth(t);
-            throw t;
-        }
+        return es.get().documents().get(indexName, TYPE, id);
     }
 
-    public void forceInsert(String indexName, String id, XContentBuilder source) throws IOException {
-        IndexRequest request = (IndexRequest) prepareInsert(indexName, id, source);
-        request.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE);
-        try {
-            client.index(request);
-            healthChecker.health();
-        } catch (Throwable t) {
-            healthChecker.unHealth(t);
-            throw t;
-        }
-    }
+    public boolean existDoc(String indexName, String id) {
+        indexName = indexNameConverter.apply(indexName);
 
-    public void forceUpdate(String indexName, String id, XContentBuilder source) throws IOException {
-        org.elasticsearch.action.update.UpdateRequest request = (org.elasticsearch.action.update.UpdateRequest) prepareUpdate(
-            indexName, id, source);
-        request.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE);
-        try {
-            client.update(request);
-            healthChecker.health();
-        } catch (Throwable t) {
-            healthChecker.unHealth(t);
-            throw t;
-        }
+        return es.get().documents().exists(indexName, TYPE, id);
     }
 
-    public InsertRequest prepareInsert(String indexName, String id, XContentBuilder source) {
-        indexName = formatIndexName(indexName);
-        return new ElasticSearchInsertRequest(indexName, TYPE, id).source(source);
-    }
+    public Optional<Documents> ids(String indexName, Iterable<String> ids) {
+        indexName = indexNameConverter.apply(indexName);
 
-    public UpdateRequest prepareUpdate(String indexName, String id, XContentBuilder source) {
-        indexName = formatIndexName(indexName);
-        return new ElasticSearchUpdateRequest(indexName, TYPE, id).doc(source);
+        return es.get().documents().mget(indexName, TYPE, ids);
     }
 
-    public int delete(String indexName, String timeBucketColumnName, long endTimeBucket) throws IOException {
-        indexName = formatIndexName(indexName);
-        Map<String, String> params = Collections.singletonMap("conflicts", "proceed");
-        String jsonString = "{" + "  \"query\": {" + "    \"range\": {" + "      \"" + timeBucketColumnName + "\": {" + "        \"lte\": " + endTimeBucket + "      }" + "    }" + "  }" + "}";
-        HttpEntity entity = new NStringEntity(jsonString, ContentType.APPLICATION_JSON);
-        Response response = client.getLowLevelClient()
-                                  .performRequest(
-                                      HttpPost.METHOD_NAME, "/" + indexName + "/_delete_by_query", params, entity);
-        log.debug("delete indexName: {}, jsonString : {}", indexName, jsonString);
-        return response.getStatusLine().getStatusCode();
+    public void forceInsert(String indexName, String id, Map<String, Object> source) {
+        IndexRequestWrapper wrapper = prepareInsert(indexName, id, source);
+        Map<String, Object> params = ImmutableMap.of("refresh", "true");
+        es.get().documents().index(wrapper.getRequest(), params);
     }
 
-    /**
-     * @since 8.7.0 SkyWalking don't use sync bulk anymore. This method is just kept for unexpected case in the future.
-     */
-    @Deprecated
-    public void synchronousBulk(BulkRequest request) {
-        request.timeout(TimeValue.timeValueMinutes(2));
-        request.setRefreshPolicy(WriteRequest.RefreshPolicy.WAIT_UNTIL);
-        request.waitForActiveShards(ActiveShardCount.ONE);
-        try {
-            int size = request.requests().size();
-            BulkResponse responses = client.bulk(request);
-            log.info("Synchronous bulk took time: {} millis, size: {}", responses.getTook().getMillis(), size);
-            healthChecker.health();
-        } catch (Throwable t) {
-            healthChecker.unHealth(t);
-        }
+    public void forceUpdate(String indexName, String id, Map<String, Object> source) {
+        UpdateRequestWrapper wrapper = prepareUpdate(indexName, id, source);
+        Map<String, Object> params = ImmutableMap.of("refresh", "true");
+        es.get().documents().update(wrapper.getRequest(), params);
     }
 
-    public BulkProcessor createBulkProcessor(int bulkActions, int flushInterval, int concurrentRequests) {
-        BulkProcessor.Listener listener = createBulkListener();
-
-        return BulkProcessor.builder(client::bulkAsync, listener)
-                            .setBulkActions(bulkActions)
-                            .setFlushInterval(TimeValue.timeValueSeconds(flushInterval))
-                            .setConcurrentRequests(concurrentRequests)
-                            .setBackoffPolicy(BackoffPolicy.exponentialBackoff(TimeValue.timeValueMillis(100), 3))
-                            .build();
+    public IndexRequestWrapper prepareInsert(String indexName, String id,
+                                             Map<String, Object> source) {
+        indexName = indexNameConverter.apply(indexName);
+        return new IndexRequestWrapper(indexName, TYPE, id, source);
     }
 
-    protected BulkProcessor.Listener createBulkListener() {
-        return new BulkProcessor.Listener() {
-            @Override
-            public void beforeBulk(long executionId, BulkRequest request) {
-                int numberOfActions = request.numberOfActions();
-                log.debug("Executing bulk [{}] with {} requests", executionId, numberOfActions);
-            }
-
-            @Override
-            public void afterBulk(long executionId, BulkRequest request, BulkResponse response) {
-                if (response.hasFailures()) {
-                    log.warn("Bulk [{}] executed with failures:[{}]", executionId, response.buildFailureMessage());
-                } else {
-                    log.info(
-                        "Bulk execution id [{}] completed in {} milliseconds, size: {}", executionId, response.getTook()
-                                                                                                              .getMillis(),
-                        request
-                            .requests()
-                            .size()
-                    );
-                }
-            }
-
-            @Override
-            public void afterBulk(long executionId, BulkRequest request, Throwable failure) {
-                log.error("Failed to execute bulk", failure);
-            }
-        };
+    public UpdateRequestWrapper prepareUpdate(String indexName, String id,
+                                              Map<String, Object> source) {
+        indexName = indexNameConverter.apply(indexName);
+        return new UpdateRequestWrapper(indexName, TYPE, id, source);
     }
 
-    public String formatIndexName(String indexName) {
-        for (final IndexNameConverter indexNameConverter : indexNameConverters) {
-            indexName = indexNameConverter.convert(indexName);
-        }
-        return indexName;
+    public BulkProcessor createBulkProcessor(int bulkActions,
+                                             int flushInterval,
+                                             int concurrentRequests) {
+        return BulkProcessor.builder()
+                            .bulkActions(bulkActions)
+                            .flushInterval(Duration.ofSeconds(flushInterval))
+                            .concurrentRequests(concurrentRequests)
+                            .build(es);
     }
 
-    @Override
-    public void registerChecker(HealthChecker healthChecker) {
-        this.healthChecker.register(healthChecker);
+    public String formatIndexName(String indexName) {
+        return indexNameConverter.apply(indexName);
     }
 }
diff --git a/oap-server/server-library/library-client/src/main/java/org/apache/skywalking/oap/server/library/client/elasticsearch/ElasticSearchInsertRequest.java b/oap-server/server-library/library-client/src/main/java/org/apache/skywalking/oap/server/library/client/elasticsearch/IndexRequestWrapper.java
similarity index 61%
rename from oap-server/server-library/library-client/src/main/java/org/apache/skywalking/oap/server/library/client/elasticsearch/ElasticSearchInsertRequest.java
rename to oap-server/server-library/library-client/src/main/java/org/apache/skywalking/oap/server/library/client/elasticsearch/IndexRequestWrapper.java
index 9c0655c..3e2cbf7 100644
--- a/oap-server/server-library/library-client/src/main/java/org/apache/skywalking/oap/server/library/client/elasticsearch/ElasticSearchInsertRequest.java
+++ b/oap-server/server-library/library-client/src/main/java/org/apache/skywalking/oap/server/library/client/elasticsearch/IndexRequestWrapper.java
@@ -17,19 +17,22 @@
 
 package org.apache.skywalking.oap.server.library.client.elasticsearch;
 
+import java.util.Map;
+import lombok.Getter;
+import org.apache.skywalking.library.elasticsearch.requests.IndexRequest;
 import org.apache.skywalking.oap.server.library.client.request.InsertRequest;
-import org.elasticsearch.action.index.IndexRequest;
-import org.elasticsearch.common.xcontent.XContentBuilder;
 
-public class ElasticSearchInsertRequest extends IndexRequest implements InsertRequest {
+@Getter
+public class IndexRequestWrapper implements InsertRequest {
+    private final IndexRequest request;
 
-    public ElasticSearchInsertRequest(String index, String type, String id) {
-        super(index, type, id);
-    }
-
-    @Override
-    public ElasticSearchInsertRequest source(XContentBuilder sourceBuilder) {
-        super.source(sourceBuilder);
-        return this;
+    public IndexRequestWrapper(String index, String type, String id,
+                               Map<String, ?> source) {
+        request = IndexRequest.builder()
+                              .index(index)
+                              .type(type)
+                              .id(id)
+                              .doc(source)
+                              .build();
     }
 }
diff --git a/oap-server/server-library/library-client/src/main/java/org/apache/skywalking/oap/server/library/client/elasticsearch/UpdateRequestWrapper.java b/oap-server/server-library/library-client/src/main/java/org/apache/skywalking/oap/server/library/client/elasticsearch/UpdateRequestWrapper.java
new file mode 100644
index 0000000..3241aa8
--- /dev/null
+++ b/oap-server/server-library/library-client/src/main/java/org/apache/skywalking/oap/server/library/client/elasticsearch/UpdateRequestWrapper.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.oap.server.library.client.elasticsearch;
+
+import java.util.Map;
+import lombok.Getter;
+import org.apache.skywalking.oap.server.library.client.request.UpdateRequest;
+
+@Getter
+public class UpdateRequestWrapper implements UpdateRequest {
+    private final org.apache.skywalking.library.elasticsearch.requests.UpdateRequest request;
+
+    public UpdateRequestWrapper(String index, String type, String id,
+                                Map<String, Object> source) {
+        request = org.apache.skywalking.library.elasticsearch.requests.UpdateRequest.builder()
+                                                                                    .index(index)
+                                                                                    .type(type)
+                                                                                    .id(id)
+                                                                                    .doc(source)
+                                                                                    .build();
+    }
+}
diff --git a/oap-server/server-library/library-client/src/main/java/org/apache/skywalking/oap/server/library/client/jdbc/hikaricp/JDBCHikariCPClient.java b/oap-server/server-library/library-client/src/main/java/org/apache/skywalking/oap/server/library/client/jdbc/hikaricp/JDBCHikariCPClient.java
index cb67151..67026ff 100644
--- a/oap-server/server-library/library-client/src/main/java/org/apache/skywalking/oap/server/library/client/jdbc/hikaricp/JDBCHikariCPClient.java
+++ b/oap-server/server-library/library-client/src/main/java/org/apache/skywalking/oap/server/library/client/jdbc/hikaricp/JDBCHikariCPClient.java
@@ -56,6 +56,7 @@ public class JDBCHikariCPClient implements Client, HealthCheckable {
 
     @Override
     public void shutdown() {
+        dataSource.close();
     }
 
     /**
@@ -65,10 +66,6 @@ public class JDBCHikariCPClient implements Client, HealthCheckable {
         return getConnection(true);
     }
 
-    public Connection getTransactionConnection() throws JDBCClientException {
-        return getConnection(false);
-    }
-
     public Connection getConnection(boolean autoCommit) throws JDBCClientException {
         try {
             Connection connection = dataSource.getConnection();
diff --git a/oap-server/server-library/library-client/src/test/java/org/apache/skywalking/library/elasticsearch/bulk/ITElasticSearch.java b/oap-server/server-library/library-client/src/test/java/org/apache/skywalking/library/elasticsearch/bulk/ITElasticSearch.java
new file mode 100644
index 0000000..51975e3
--- /dev/null
+++ b/oap-server/server-library/library-client/src/test/java/org/apache/skywalking/library/elasticsearch/bulk/ITElasticSearch.java
@@ -0,0 +1,255 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.library.elasticsearch.bulk;
+
+import com.google.common.collect.ImmutableMap;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Function;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.skywalking.apm.util.StringUtil;
+import org.apache.skywalking.library.elasticsearch.requests.search.Query;
+import org.apache.skywalking.library.elasticsearch.requests.search.Search;
+import org.apache.skywalking.library.elasticsearch.requests.search.SearchBuilder;
+import org.apache.skywalking.library.elasticsearch.response.Document;
+import org.apache.skywalking.library.elasticsearch.response.Index;
+import org.apache.skywalking.library.elasticsearch.response.Mappings;
+import org.apache.skywalking.library.elasticsearch.response.search.SearchResponse;
+import org.apache.skywalking.oap.server.library.client.elasticsearch.ElasticSearchClient;
+import org.apache.skywalking.oap.server.library.client.elasticsearch.IndexRequestWrapper;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.testcontainers.elasticsearch.ElasticsearchContainer;
+import org.testcontainers.utility.DockerImageName;
+
+@Slf4j
+@RequiredArgsConstructor
+@RunWith(Parameterized.class)
+public class ITElasticSearch {
+
+    @Parameterized.Parameters(name = "version: {0}, namespace: {1}")
+    public static Collection<Object[]> versions() {
+        return Arrays.asList(new Object[][] {
+            {"6.3.2", ""},
+            {"6.3.2", "test"},
+            {"7.8.0", ""},
+            {"7.8.0", "test"}
+        });
+    }
+
+    private final String version;
+    private final String namespace;
+
+    private ElasticsearchContainer server;
+
+    private ElasticSearchClient client;
+
+    @Before
+    public void before() throws Exception {
+        server = new ElasticsearchContainer(
+            DockerImageName.parse("docker.elastic.co/elasticsearch/elasticsearch-oss")
+                           .withTag(version)
+        );
+        server.start();
+
+        client = new ElasticSearchClient(
+            server.getHttpHostAddress(),
+            "http", "", "", "test", "test",
+            indexNameConverter(namespace), 500, 6000,
+            0
+        );
+        client.connect();
+    }
+
+    @After
+    public void after() throws IOException {
+        client.shutdown();
+        server.stop();
+    }
+
+    @Test
+    public void indexOperate() {
+        Map<String, Object> settings = new HashMap<>();
+        settings.put("number_of_shards", 2);
+        settings.put("number_of_replicas", 2);
+
+        final Mappings mappings =
+            Mappings.builder()
+                    .type("type")
+                    .properties(ImmutableMap.of(
+                        "column1",
+                        ImmutableMap.of("type", "text")
+                    ))
+                    .build();
+
+        String indexName = "test_index_operate";
+        client.createIndex(indexName, mappings, settings);
+        Assert.assertTrue(client.isExistsIndex(indexName));
+
+        Index index = client.getIndex(indexName).get();
+        log.info(index.toString());
+
+        Assert.assertEquals(
+            "2",
+            ((Map<String, ?>) index.getSettings().get("index")).get("number_of_shards")
+        );
+        Assert.assertEquals(
+            "2",
+            ((Map<String, ?>) index.getSettings().get("index")).get("number_of_replicas")
+        );
+
+        Assert.assertEquals(
+            "text",
+            ((Map<String, ?>) index.getMappings().getProperties().get("column1")).get("type")
+        );
+    }
+
+    @Test
+    public void documentOperate() {
+        String id = String.valueOf(System.currentTimeMillis());
+
+        Map<String, Object> builder = ImmutableMap.<String, Object>builder()
+                                                  .put("user", "kimchy")
+                                                  .put("post_date", "2009-11-15T14:12:12")
+                                                  .put("message", "trying out Elasticsearch")
+                                                  .build();
+
+        String indexName = "test_document_operate";
+        client.forceInsert(indexName, id, builder);
+
+        Optional<Document> response = client.get(indexName, id);
+        Assert.assertEquals("kimchy", response.get().getSource().get("user"));
+        Assert.assertEquals("trying out Elasticsearch", response.get().getSource().get("message"));
+
+        builder = ImmutableMap.<String, Object>builder().put("user", "pengys").build();
+        client.forceUpdate(indexName, id, builder);
+
+        response = client.get(indexName, id);
+        Assert.assertEquals("pengys", response.get().getSource().get("user"));
+        Assert.assertEquals("trying out Elasticsearch", response.get().getSource().get("message"));
+
+        SearchBuilder sourceBuilder = Search.builder();
+        sourceBuilder.query(Query.term("user", "pengys"));
+        SearchResponse searchResponse = client.search(indexName, sourceBuilder.build());
+        Assert.assertEquals("trying out Elasticsearch", searchResponse.getHits()
+                                                                      .getHits()
+                                                                      .iterator()
+                                                                      .next()
+                                                                      .getSource()
+                                                                      .get("message"));
+    }
+
+    @Test
+    public void templateOperate() {
+        Map<String, Object> settings = new HashMap<>();
+        settings.put("number_of_shards", 1);
+        settings.put("number_of_replicas", 0);
+        settings.put("index.refresh_interval", "3s");
+        settings.put("analysis.analyzer.oap_analyzer.type", "stop");
+
+        Mappings mapping =
+            Mappings.builder()
+                    .type("type")
+                    .properties(
+                        ImmutableMap.of(
+                            "name", ImmutableMap.of("type", "text")
+                        )
+                    )
+                    .build();
+        String indexName = "template_operate";
+
+        client.createOrUpdateTemplate(indexName, settings, mapping, 0);
+
+        Assert.assertTrue(client.isExistsTemplate(indexName));
+
+        Map<String, Object> builder = ImmutableMap.of("name", "pengys");
+        client.forceInsert(indexName + "-2019", "testid", builder);
+        Index index = client.getIndex(indexName + "-2019").get();
+        log.info(index.toString());
+
+        Assert.assertEquals(
+            "1",
+            ((Map<String, Object>) index.getSettings().get("index")).get("number_of_shards")
+        );
+        Assert.assertEquals(
+            "0",
+            ((Map<String, ?>) index.getSettings().get("index")).get("number_of_replicas")
+        );
+        client.deleteTemplate(indexName);
+        Assert.assertFalse(client.isExistsTemplate(indexName));
+    }
+
+    @Test
+    public void bulk() {
+        BulkProcessor bulkProcessor = client.createBulkProcessor(2000, 10, 2);
+
+        Map<String, String> source = new HashMap<>();
+        source.put("column1", "value1");
+        source.put("column2", "value2");
+
+        for (int i = 0; i < 100; i++) {
+            IndexRequestWrapper
+                indexRequest =
+                new IndexRequestWrapper("bulk_insert_test", "type", String.valueOf(i), source);
+            bulkProcessor.add(indexRequest.getRequest());
+        }
+
+        bulkProcessor.flush();
+    }
+
+    @Test
+    public void timeSeriesOperate() {
+        final String indexName = "test_time_series_operate";
+        final String timeSeriesIndexName = indexName + "-2019";
+        final Mappings mapping =
+            Mappings.builder()
+                    .type("type")
+                    .properties(ImmutableMap.of("name", ImmutableMap.of("type", "text")))
+                    .build();
+
+        client.createOrUpdateTemplate(indexName, new HashMap<>(), mapping, 0);
+
+        Map<String, Object> builder = ImmutableMap.of("name", "pengys");
+        client.forceInsert(timeSeriesIndexName, "testid", builder);
+
+        Collection<String> indexes = client.retrievalIndexByAliases(indexName);
+        Assert.assertEquals(1, indexes.size());
+        String index = indexes.iterator().next();
+        Assert.assertTrue(client.deleteByIndexName(index));
+        Assert.assertFalse(client.isExistsIndex(timeSeriesIndexName));
+        client.deleteTemplate(indexName);
+    }
+
+    private static Function<String, String> indexNameConverter(String namespace) {
+        return indexName -> {
+            if (StringUtil.isNotEmpty(namespace)) {
+                return namespace + "_" + indexName;
+            }
+            return indexName;
+        };
+    }
+}
diff --git a/oap-server/server-library/library-client/src/test/java/org/apache/skywalking/oap/server/library/client/elasticsearch/ITElasticSearchClient.java b/oap-server/server-library/library-client/src/test/java/org/apache/skywalking/oap/server/library/client/elasticsearch/ITElasticSearchClient.java
deleted file mode 100644
index ae9a9a2..0000000
--- a/oap-server/server-library/library-client/src/test/java/org/apache/skywalking/oap/server/library/client/elasticsearch/ITElasticSearchClient.java
+++ /dev/null
@@ -1,307 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package org.apache.skywalking.oap.server.library.client.elasticsearch;
-
-import com.google.gson.Gson;
-import com.google.gson.JsonObject;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
-import org.apache.http.client.methods.HttpGet;
-import org.apache.skywalking.apm.util.StringUtil;
-import org.elasticsearch.action.admin.indices.get.GetIndexRequest;
-import org.elasticsearch.action.bulk.BulkProcessor;
-import org.elasticsearch.action.get.GetResponse;
-import org.elasticsearch.action.index.IndexRequest;
-import org.elasticsearch.action.search.SearchResponse;
-import org.elasticsearch.client.Response;
-import org.elasticsearch.client.RestHighLevelClient;
-import org.elasticsearch.common.xcontent.XContentBuilder;
-import org.elasticsearch.common.xcontent.XContentFactory;
-import org.elasticsearch.index.query.QueryBuilders;
-import org.elasticsearch.search.builder.SearchSourceBuilder;
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-import org.powermock.reflect.Whitebox;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class ITElasticSearchClient {
-
-    private static final Logger LOGGER = LoggerFactory.getLogger(ITElasticSearchClient.class);
-
-    private ElasticSearchClient client;
-
-    private final String namespace;
-
-    public ITElasticSearchClient() {
-        namespace = "default-test-namespace";
-    }
-
-    protected ITElasticSearchClient(String namespace) {
-        this.namespace = namespace;
-    }
-
-    @Before
-    public void before() throws Exception {
-        final String esAddress = System.getProperty("elastic.search.address");
-        final String esProtocol = System.getProperty("elastic.search.protocol");
-        client = new ElasticSearchClient(esAddress, esProtocol, "", "", "test", "test",
-                                         indexNameConverters(namespace), 500, 6000
-        );
-        client.connect();
-    }
-
-    @After
-    public void after() throws IOException {
-        client.shutdown();
-    }
-
-    @Test
-    public void indexOperate() throws IOException {
-        Map<String, Object> settings = new HashMap<>();
-        settings.put("number_of_shards", 2);
-        settings.put("number_of_replicas", 2);
-
-        Map<String, Object> doc = new HashMap<>();
-
-        JsonObject properties = new JsonObject();
-        doc.put("properties", properties);
-
-        JsonObject column = new JsonObject();
-        column.addProperty("type", "text");
-        properties.add("column1", column);
-
-        String indexName = "test_index_operate";
-        client.createIndex(indexName, settings, doc);
-        Assert.assertTrue(client.isExistsIndex(indexName));
-
-        JsonObject index = getIndex(indexName);
-        LOGGER.info(index.toString());
-
-        Assert.assertEquals(2, index.getAsJsonObject(indexName)
-                                    .getAsJsonObject("settings")
-                                    .getAsJsonObject("index")
-                                    .get("number_of_shards")
-                                    .getAsInt());
-        Assert.assertEquals(2, index.getAsJsonObject(indexName)
-                                    .getAsJsonObject("settings")
-                                    .getAsJsonObject("index")
-                                    .get("number_of_replicas")
-                                    .getAsInt());
-
-        Assert.assertEquals("text", index.getAsJsonObject(indexName)
-                                         .getAsJsonObject("mappings")
-                                         .getAsJsonObject("type")
-                                         .getAsJsonObject("properties")
-                                         .getAsJsonObject("column1")
-                                         .get("type")
-                                         .getAsString());
-
-        Assert.assertTrue(client.deleteByModelName(indexName));
-    }
-
-    @Test
-    public void documentOperate() throws IOException {
-        String id = String.valueOf(System.currentTimeMillis());
-
-        XContentBuilder builder = XContentFactory.jsonBuilder()
-                                                 .startObject()
-                                                 .field("user", "kimchy")
-                                                 .field("post_date", "2009-11-15T14:12:12")
-                                                 .field("message", "trying out Elasticsearch")
-                                                 .endObject();
-
-        String indexName = "test_document_operate";
-        client.forceInsert(indexName, id, builder);
-
-        GetResponse response = client.get(indexName, id);
-        Assert.assertEquals("kimchy", response.getSource().get("user"));
-        Assert.assertEquals("trying out Elasticsearch", response.getSource().get("message"));
-
-        builder = XContentFactory.jsonBuilder().startObject().field("user", "pengys").endObject();
-        client.forceUpdate(indexName, id, builder);
-
-        response = client.get(indexName, id);
-        Assert.assertEquals("pengys", response.getSource().get("user"));
-        Assert.assertEquals("trying out Elasticsearch", response.getSource().get("message"));
-
-        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
-        sourceBuilder.query(QueryBuilders.termQuery("user", "pengys"));
-        SearchResponse searchResponse = client.search(indexName, sourceBuilder);
-        Assert.assertEquals("trying out Elasticsearch", searchResponse.getHits().getHits()[0].getSourceAsMap()
-                                                                                             .get("message"));
-    }
-
-    @Test
-    public void templateOperate() throws IOException {
-        Map<String, Object> settings = new HashMap<>();
-        settings.put("number_of_shards", 1);
-        settings.put("number_of_replicas", 0);
-        settings.put("index.refresh_interval", "3s");
-        settings.put("analysis.analyzer.oap_analyzer.type", "stop");
-
-        Map<String, Object> mapping = new HashMap<>();
-        Map<String, Object> doc = new HashMap<>();
-        mapping.put("type", doc);
-
-        JsonObject properties = new JsonObject();
-        doc.put("properties", properties);
-
-        JsonObject column = new JsonObject();
-        column.addProperty("type", "text");
-        properties.add("name", column);
-
-        String indexName = "template_operate";
-
-        client.createOrUpdateTemplate(indexName, settings, mapping, 0);
-
-        Assert.assertTrue(client.isExistsTemplate(indexName));
-
-        XContentBuilder builder = XContentFactory.jsonBuilder().startObject().field("name", "pengys").endObject();
-        client.forceInsert(indexName + "-2019", "testid", builder);
-        JsonObject index = getIndex(indexName + "-2019");
-        LOGGER.info(index.toString());
-
-        Assert.assertEquals(1, index.getAsJsonObject(indexName + "-2019")
-                                    .getAsJsonObject("settings")
-                                    .getAsJsonObject("index")
-                                    .get("number_of_shards")
-                                    .getAsInt());
-        Assert.assertEquals(0, index.getAsJsonObject(indexName + "-2019")
-                                    .getAsJsonObject("settings")
-                                    .getAsJsonObject("index")
-                                    .get("number_of_replicas")
-                                    .getAsInt());
-        client.deleteTemplate(indexName);
-        Assert.assertFalse(client.isExistsTemplate(indexName));
-    }
-
-    @Test
-    public void bulk() throws InterruptedException {
-        BulkProcessor bulkProcessor = client.createBulkProcessor(2000, 10, 2);
-
-        Map<String, String> source = new HashMap<>();
-        source.put("column1", "value1");
-        source.put("column2", "value2");
-
-        for (int i = 0; i < 100; i++) {
-            IndexRequest indexRequest = new IndexRequest("bulk_insert_test", "type", String.valueOf(i));
-            indexRequest.source(source);
-            bulkProcessor.add(indexRequest);
-        }
-
-        bulkProcessor.flush();
-        bulkProcessor.awaitClose(2, TimeUnit.SECONDS);
-    }
-
-    @Test
-    public void timeSeriesOperate() throws IOException {
-        String indexName = "test_time_series_operate";
-        String timeSeriesIndexName = indexName + "-2019";
-
-        Map<String, Object> mapping = new HashMap<>();
-        Map<String, Object> doc = new HashMap<>();
-        mapping.put("type", doc);
-
-        JsonObject properties = new JsonObject();
-        doc.put("properties", properties);
-
-        JsonObject column = new JsonObject();
-        column.addProperty("type", "text");
-        properties.add("name", column);
-
-        client.createOrUpdateTemplate(indexName, new HashMap<>(), mapping, 0);
-
-        XContentBuilder builder = XContentFactory.jsonBuilder().startObject().field("name", "pengys").endObject();
-        client.forceInsert(timeSeriesIndexName, "testid", builder);
-
-        List<String> indexes = client.retrievalIndexByAliases(indexName);
-        Assert.assertEquals(1, indexes.size());
-        String index = indexes.get(0);
-        Assert.assertTrue(client.deleteByIndexName(index));
-        Assert.assertFalse(client.isExistsIndex(timeSeriesIndexName));
-        client.deleteTemplate(indexName);
-    }
-
-    private JsonObject getIndex(String indexName) throws IOException {
-        indexName = client.formatIndexName(indexName);
-        GetIndexRequest request = new GetIndexRequest();
-        request.indices(indexName);
-
-        Response response = getRestHighLevelClient().getLowLevelClient()
-                                                    .performRequest(HttpGet.METHOD_NAME, "/" + indexName);
-        InputStreamReader reader = new InputStreamReader(response.getEntity().getContent());
-        Gson gson = new Gson();
-        return undoFormatIndexName(gson.fromJson(reader, JsonObject.class));
-    }
-
-    private RestHighLevelClient getRestHighLevelClient() {
-        return (RestHighLevelClient) Whitebox.getInternalState(client, "client");
-    }
-
-    private JsonObject undoFormatIndexName(JsonObject index) {
-        if (StringUtil.isNotEmpty(namespace) && index != null && index.size() > 0) {
-            LOGGER.info("UndoFormatIndexName before " + index.toString());
-            String namespacePrefix = namespace + "_";
-            index.entrySet().forEach(entry -> {
-                String oldIndexName = entry.getKey();
-                if (oldIndexName.startsWith(namespacePrefix)) {
-                    index.add(oldIndexName.substring(namespacePrefix.length()), entry.getValue());
-                    index.remove(oldIndexName);
-                } else {
-                    throw new RuntimeException(
-                        "The indexName must contain the " + namespace + " prefix, but it is " + entry
-                            .getKey());
-                }
-            });
-            LOGGER.info("UndoFormatIndexName after " + index.toString());
-        }
-        return index;
-    }
-
-    private static List<IndexNameConverter> indexNameConverters(String namespace) {
-        List<IndexNameConverter> converters = new ArrayList<>();
-        converters.add(new NamespaceConverter(namespace));
-        return converters;
-    }
-
-    private static class NamespaceConverter implements IndexNameConverter {
-        private final String namespace;
-
-        public NamespaceConverter(final String namespace) {
-            this.namespace = namespace;
-        }
-
-        @Override
-        public String convert(final String indexName) {
-            if (StringUtil.isNotEmpty(namespace)) {
-                return namespace + "_" + indexName;
-            }
-
-            return indexName;
-        }
-    }
-}
diff --git a/test/e2e/e2e-common/pom.xml b/oap-server/server-library/library-elasticsearch-client/pom.xml
similarity index 62%
copy from test/e2e/e2e-common/pom.xml
copy to oap-server/server-library/library-elasticsearch-client/pom.xml
index ccee409..bd4a904 100644
--- a/test/e2e/e2e-common/pom.xml
+++ b/oap-server/server-library/library-elasticsearch-client/pom.xml
@@ -21,32 +21,39 @@
          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">
     <parent>
-        <artifactId>apache-skywalking-e2e</artifactId>
+        <artifactId>server-library</artifactId>
         <groupId>org.apache.skywalking</groupId>
-        <version>1.0.0</version>
+        <version>8.8.0-SNAPSHOT</version>
     </parent>
-
     <modelVersion>4.0.0</modelVersion>
 
-    <artifactId>e2e-common</artifactId>
-
-    <description>Some fundamental classes used in E2E tests</description>
-
-    <properties>
-        <version.system-rules>1.19.0</version.system-rules>
-    </properties>
+    <artifactId>library-elasticsearch-client</artifactId>
+    <description>
+        An ElasticSearch client built on top of ElasticSearch REST API.
+        Note that this client only covers part of the features that are used in SkyWalking,
+        and it's not for general use.
+    </description>
 
     <dependencies>
         <dependency>
-            <groupId>org.junit.jupiter</groupId>
-            <artifactId>junit-jupiter</artifactId>
-            <optional>true</optional>
+            <groupId>com.linecorp.armeria</groupId>
+            <artifactId>armeria</artifactId>
         </dependency>
 
         <dependency>
             <groupId>org.testcontainers</groupId>
             <artifactId>testcontainers</artifactId>
-            <optional>true</optional>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.testcontainers</groupId>
+            <artifactId>elasticsearch</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.awaitility</groupId>
+            <artifactId>awaitility</artifactId>
+            <scope>test</scope>
         </dependency>
     </dependencies>
-</project>
\ No newline at end of file
+</project>
diff --git a/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/ElasticSearch.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/ElasticSearch.java
new file mode 100644
index 0000000..54226e3
--- /dev/null
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/ElasticSearch.java
@@ -0,0 +1,182 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.library.elasticsearch;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.linecorp.armeria.client.ClientFactory;
+import com.linecorp.armeria.client.Endpoint;
+import com.linecorp.armeria.client.WebClient;
+import com.linecorp.armeria.client.WebClientBuilder;
+import com.linecorp.armeria.client.endpoint.EndpointGroup;
+import com.linecorp.armeria.client.logging.LoggingClient;
+import com.linecorp.armeria.client.retry.RetryRule;
+import com.linecorp.armeria.client.retry.RetryingClient;
+import com.linecorp.armeria.common.HttpData;
+import com.linecorp.armeria.common.HttpStatus;
+import com.linecorp.armeria.common.SessionProtocol;
+import com.linecorp.armeria.common.auth.BasicToken;
+import com.linecorp.armeria.common.util.Exceptions;
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Consumer;
+import lombok.Getter;
+import lombok.experimental.Accessors;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.skywalking.apm.util.StringUtil;
+import org.apache.skywalking.library.elasticsearch.client.AliasClient;
+import org.apache.skywalking.library.elasticsearch.client.DocumentClient;
+import org.apache.skywalking.library.elasticsearch.client.IndexClient;
+import org.apache.skywalking.library.elasticsearch.client.SearchClient;
+import org.apache.skywalking.library.elasticsearch.client.TemplateClient;
+import org.apache.skywalking.library.elasticsearch.requests.search.Search;
+import org.apache.skywalking.library.elasticsearch.response.NodeInfo;
+import org.apache.skywalking.library.elasticsearch.response.search.SearchResponse;
+
+@Slf4j
+@Accessors(fluent = true)
+public final class ElasticSearch implements Closeable {
+    private final ObjectMapper mapper = new ObjectMapper()
+        .setSerializationInclusion(JsonInclude.Include.NON_NULL)
+        .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+
+    @Getter
+    private final WebClient client;
+    @Getter
+    private final CompletableFuture<ElasticSearchVersion> version;
+
+    private final EndpointGroup endpointGroup;
+    private final ClientFactory clientFactory;
+    private final Consumer<List<Endpoint>> healthyEndpointListener;
+
+    private final TemplateClient templateClient;
+    private final IndexClient indexClient;
+    private final DocumentClient documentClient;
+    private final AliasClient aliasClient;
+    private final SearchClient searchClient;
+
+    ElasticSearch(SessionProtocol protocol,
+                  String username, String password,
+                  EndpointGroup endpointGroup,
+                  ClientFactory clientFactory,
+                  Consumer<Boolean> healthyListener) {
+        this.endpointGroup = endpointGroup;
+        this.clientFactory = clientFactory;
+        if (healthyListener != null) {
+            healthyEndpointListener = it -> healthyListener.accept(!it.isEmpty());
+        } else {
+            healthyEndpointListener = it -> {
+            };
+        }
+
+        final WebClientBuilder builder =
+            WebClient.builder(protocol, endpointGroup)
+                     .factory(clientFactory)
+                     .decorator(LoggingClient.builder()
+                                             .logger(log)
+                                             .newDecorator())
+                     .decorator(RetryingClient.builder(RetryRule.failsafe())
+                                              .maxTotalAttempts(3)
+                                              .newDecorator());
+        if (StringUtil.isNotBlank(username) && StringUtil.isNotBlank(password)) {
+            builder.auth(BasicToken.of(username, password));
+        }
+        client = builder.build();
+        version = new CompletableFuture<>();
+
+        templateClient = new TemplateClient(version, client);
+        documentClient = new DocumentClient(version, client);
+        indexClient = new IndexClient(version, client);
+        aliasClient = new AliasClient(version, client);
+        searchClient = new SearchClient(version, client);
+    }
+
+    public static ElasticSearchBuilder builder() {
+        return new ElasticSearchBuilder();
+    }
+
+    public CompletableFuture<ElasticSearchVersion> connect() {
+        final CompletableFuture<ElasticSearchVersion> future =
+            client.get("/").aggregate().thenApply(response -> {
+                final HttpStatus status = response.status();
+                if (status != HttpStatus.OK) {
+                    throw new RuntimeException(
+                        "Failed to connect to ElasticSearch server: " + response.contentUtf8());
+                }
+                try (final HttpData content = response.content();
+                     final InputStream is = content.toInputStream()) {
+                    final NodeInfo node = mapper.readValue(is, NodeInfo.class);
+                    final String vn = node.getVersion().getNumber();
+                    final String distribution = node.getVersion().getDistribution();
+                    return ElasticSearchVersion.of(distribution, vn);
+                } catch (IOException e) {
+                    return Exceptions.throwUnsafely(e);
+                }
+            });
+        future.whenComplete((v, throwable) -> {
+            if (throwable != null) {
+                final RuntimeException cause =
+                    new RuntimeException("Failed to determine ElasticSearch version", throwable);
+                version.completeExceptionally(cause);
+                healthyEndpointListener.accept(Collections.emptyList());
+                return;
+            }
+            log.info("ElasticSearch version is: {}", v);
+            version.complete(v);
+        });
+        endpointGroup.addListener(healthyEndpointListener);
+        return future;
+    }
+
+    public TemplateClient templates() {
+        return templateClient;
+    }
+
+    public DocumentClient documents() {
+        return documentClient;
+    }
+
+    public IndexClient index() {
+        return indexClient;
+    }
+
+    public AliasClient alias() {
+        return aliasClient;
+    }
+
+    public SearchResponse search(Search search, Map<String, ?> params, String... index) {
+        return searchClient.search(search, params, index);
+    }
+
+    public SearchResponse search(Search search, String... index) {
+        return search(search, null, index);
+    }
+
+    @Override
+    public void close() {
+        endpointGroup.removeListener(healthyEndpointListener);
+        clientFactory.close();
+        endpointGroup.close();
+    }
+}
diff --git a/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/ElasticSearchBuilder.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/ElasticSearchBuilder.java
new file mode 100644
index 0000000..d154f32
--- /dev/null
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/ElasticSearchBuilder.java
@@ -0,0 +1,189 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.library.elasticsearch;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.net.HttpHeaders;
+import com.linecorp.armeria.client.ClientFactory;
+import com.linecorp.armeria.client.ClientFactoryBuilder;
+import com.linecorp.armeria.client.Endpoint;
+import com.linecorp.armeria.client.endpoint.EndpointGroup;
+import com.linecorp.armeria.client.endpoint.healthcheck.HealthCheckedEndpointGroup;
+import com.linecorp.armeria.client.endpoint.healthcheck.HealthCheckedEndpointGroupBuilder;
+import com.linecorp.armeria.common.SessionProtocol;
+import com.linecorp.armeria.common.auth.BasicToken;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.KeyStore;
+import java.time.Duration;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+import javax.net.ssl.TrustManagerFactory;
+import lombok.SneakyThrows;
+import org.apache.skywalking.apm.util.StringUtil;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.Objects.requireNonNull;
+import static org.apache.skywalking.apm.util.StringUtil.isNotBlank;
+
+public final class ElasticSearchBuilder {
+    private static final int NUM_PROC = Runtime.getRuntime().availableProcessors();
+
+    private SessionProtocol protocol = SessionProtocol.HTTP;
+
+    private String username;
+
+    private String password;
+
+    private Duration healthCheckRetryInterval = Duration.ofSeconds(30);
+
+    private final ImmutableList.Builder<String> endpoints = ImmutableList.builder();
+
+    private String trustStorePath;
+
+    private String trustStorePass;
+
+    private Duration connectTimeout = Duration.ofMillis(500);
+
+    private Consumer<Boolean> healthyListener;
+
+    private int numHttpClientThread;
+
+    public ElasticSearchBuilder protocol(String protocol) {
+        checkArgument(isNotBlank(protocol), "protocol cannot be blank");
+        this.protocol = SessionProtocol.of(protocol);
+        return this;
+    }
+
+    public ElasticSearchBuilder username(String username) {
+        this.username = requireNonNull(username, "username");
+        return this;
+    }
+
+    public ElasticSearchBuilder password(String password) {
+        this.password = requireNonNull(password, "password");
+        return this;
+    }
+
+    public ElasticSearchBuilder endpoints(Iterable<String> endpoints) {
+        requireNonNull(endpoints, "endpoints");
+        this.endpoints.addAll(endpoints);
+        return this;
+    }
+
+    public ElasticSearchBuilder endpoints(String... endpoints) {
+        return endpoints(Arrays.asList(endpoints));
+    }
+
+    public ElasticSearchBuilder healthCheckRetryInterval(Duration healthCheckRetryInterval) {
+        requireNonNull(healthCheckRetryInterval, "healthCheckRetryInterval");
+        this.healthCheckRetryInterval = healthCheckRetryInterval;
+        return this;
+    }
+
+    public ElasticSearchBuilder trustStorePath(String trustStorePath) {
+        requireNonNull(trustStorePath, "trustStorePath");
+        this.trustStorePath = trustStorePath;
+        return this;
+    }
+
+    public ElasticSearchBuilder trustStorePass(String trustStorePass) {
+        requireNonNull(trustStorePass, "trustStorePass");
+        this.trustStorePass = trustStorePass;
+        return this;
+    }
+
+    public ElasticSearchBuilder connectTimeout(int connectTimeout) {
+        checkArgument(connectTimeout > 0, "connectTimeout must be positive");
+        this.connectTimeout = Duration.ofMillis(connectTimeout);
+        return this;
+    }
+
+    public ElasticSearchBuilder healthyListener(Consumer<Boolean> healthyListener) {
+        requireNonNull(healthyListener, "healthyListener");
+        this.healthyListener = healthyListener;
+        return this;
+    }
+
+    public ElasticSearchBuilder numHttpClientThread(int numHttpClientThread) {
+        this.numHttpClientThread = numHttpClientThread;
+        return this;
+    }
+
+    @SneakyThrows
+    public ElasticSearch build() {
+        final List<Endpoint> endpoints =
+            this.endpoints.build().stream()
+                          .filter(StringUtil::isNotBlank)
+                          .map(Endpoint::parse)
+                          .collect(Collectors.toList());
+        final ClientFactoryBuilder factoryBuilder =
+            ClientFactory.builder()
+                         .connectTimeout(connectTimeout)
+                         .useHttp2Preface(false)
+                         .workerGroup(numHttpClientThread > 0 ? numHttpClientThread : NUM_PROC);
+
+        if (StringUtil.isNotBlank(trustStorePath)) {
+            final TrustManagerFactory trustManagerFactory =
+                TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+            final KeyStore truststore = KeyStore.getInstance("jks");
+            try (final InputStream is = Files.newInputStream(Paths.get(trustStorePath))) {
+                truststore.load(is, trustStorePass.toCharArray());
+            }
+            trustManagerFactory.init(truststore);
+
+            factoryBuilder.tlsCustomizer(
+                sslContextBuilder -> sslContextBuilder.trustManager(trustManagerFactory));
+        }
+
+        final ClientFactory clientFactory = factoryBuilder.build();
+
+        final HealthCheckedEndpointGroupBuilder endpointGroupBuilder =
+            HealthCheckedEndpointGroup.builder(EndpointGroup.of(endpoints), "_cluster/health")
+                                      .protocol(protocol)
+                                      .useGet(true)
+                                      .clientFactory(clientFactory)
+                                      .retryInterval(healthCheckRetryInterval)
+                                      .withClientOptions(options -> {
+                                          options.decorator((delegate, ctx, req) -> {
+                                              ctx.logBuilder().name("health-check");
+                                              return delegate.execute(ctx, req);
+                                          });
+                                          return options;
+                                      });
+        if (StringUtil.isNotBlank(username) && StringUtil.isNotBlank(password)) {
+            endpointGroupBuilder.withClientOptions(it -> it.setHeader(
+                HttpHeaders.AUTHORIZATION,
+                BasicToken.of(username, password).asHeaderValue()
+            ));
+        }
+        final HealthCheckedEndpointGroup endpointGroup = endpointGroupBuilder.build();
+
+        return new ElasticSearch(
+            protocol,
+            username,
+            password,
+            endpointGroup,
+            clientFactory,
+            healthyListener
+        );
+    }
+}
diff --git a/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/ElasticSearchVersion.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/ElasticSearchVersion.java
new file mode 100644
index 0000000..733f6f9
--- /dev/null
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/ElasticSearchVersion.java
@@ -0,0 +1,99 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.library.elasticsearch;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.apache.skywalking.library.elasticsearch.requests.factory.Codec;
+import org.apache.skywalking.library.elasticsearch.requests.factory.RequestFactory;
+import org.apache.skywalking.library.elasticsearch.requests.factory.v6.V6RequestFactory;
+import org.apache.skywalking.library.elasticsearch.requests.factory.v6.codec.V6Codec;
+import org.apache.skywalking.library.elasticsearch.requests.factory.v7.V78RequestFactory;
+import org.apache.skywalking.library.elasticsearch.requests.factory.v7.V7RequestFactory;
+import org.apache.skywalking.library.elasticsearch.requests.factory.v7.codec.V7Codec;
+
+public final class ElasticSearchVersion {
+    private final String distribution;
+    private final int major;
+    private final int minor;
+
+    private final RequestFactory requestFactory;
+    private final Codec codec;
+
+    private ElasticSearchVersion(final String distribution, final int major, final int minor) {
+        this.distribution = distribution;
+        this.major = major;
+        this.minor = minor;
+
+        if (distribution.equalsIgnoreCase("OpenSearch")) {
+            requestFactory = new V78RequestFactory(this);
+            codec = V7Codec.INSTANCE;
+            return;
+        }
+
+        if (distribution.equalsIgnoreCase("ElasticSearch")) {
+            if (major == 6) { // 6.x
+                requestFactory = new V6RequestFactory(this);
+                codec = V6Codec.INSTANCE;
+                return;
+            }
+            if (major == 7) {
+                codec = V7Codec.INSTANCE;
+                if (minor < 8) { // [7.0, 7.8)
+                    requestFactory = new V7RequestFactory(this);
+                } else { // [7.8, 8.0)
+                    requestFactory = new V78RequestFactory(this);
+                }
+                return;
+            }
+        }
+        throw new UnsupportedOperationException("Unsupported version: " + this);
+    }
+
+    @Override
+    public String toString() {
+        return distribution + " " + major + "." + minor;
+    }
+
+    private static final Pattern REGEX = Pattern.compile("(\\d+)\\.(\\d+).*");
+
+    public static ElasticSearchVersion of(String distribution, String version) {
+        final Matcher matcher = REGEX.matcher(version);
+        if (!matcher.matches()) {
+            throw new IllegalArgumentException("Failed to parse version: " + version);
+        }
+        final int major = Integer.parseInt(matcher.group(1));
+        final int minor = Integer.parseInt(matcher.group(2));
+        return new ElasticSearchVersion(distribution, major, minor);
+    }
+
+    /**
+     * Returns a {@link RequestFactory} that is responsible to compose correct requests according to
+     * the syntax of specific {@link ElasticSearchVersion}.
+     */
+    public RequestFactory requestFactory() {
+        return requestFactory;
+    }
+
+    /**
+     * Returns a {@link Codec} to encode the requests and decode the response.
+     */
+    public Codec codec() {
+        return codec;
+    }
+}
diff --git a/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/bulk/BulkProcessor.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/bulk/BulkProcessor.java
new file mode 100644
index 0000000..a8f72ec
--- /dev/null
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/bulk/BulkProcessor.java
@@ -0,0 +1,152 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.library.elasticsearch.bulk;
+
+import com.linecorp.armeria.common.HttpStatus;
+import com.linecorp.armeria.common.util.Exceptions;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.skywalking.library.elasticsearch.ElasticSearch;
+import org.apache.skywalking.library.elasticsearch.requests.IndexRequest;
+import org.apache.skywalking.library.elasticsearch.requests.UpdateRequest;
+import org.apache.skywalking.library.elasticsearch.requests.factory.RequestFactory;
+
+import static java.util.Objects.requireNonNull;
+
+@Slf4j
+public final class BulkProcessor {
+    private final ArrayBlockingQueue<Object> requests;
+
+    private final AtomicReference<ElasticSearch> es;
+    private final int bulkActions;
+    private final Semaphore semaphore;
+
+    public static BulkProcessorBuilder builder() {
+        return new BulkProcessorBuilder();
+    }
+
+    BulkProcessor(
+        final AtomicReference<ElasticSearch> es, final int bulkActions,
+        final Duration flushInterval, final int concurrentRequests) {
+        requireNonNull(flushInterval, "flushInterval");
+
+        this.es = requireNonNull(es, "es");
+        this.bulkActions = bulkActions;
+        this.semaphore = new Semaphore(concurrentRequests > 0 ? concurrentRequests : 1);
+        this.requests = new ArrayBlockingQueue<>(bulkActions + 1);
+
+        final ScheduledThreadPoolExecutor scheduler = new ScheduledThreadPoolExecutor(
+            1, r -> {
+            final Thread thread = new Thread(r);
+            thread.setName("ElasticSearch BulkProcessor");
+            return thread;
+        });
+        scheduler.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
+        scheduler.setContinueExistingPeriodicTasksAfterShutdownPolicy(false);
+        scheduler.setRemoveOnCancelPolicy(true);
+        scheduler.scheduleWithFixedDelay(
+            this::flush, 0, flushInterval.getSeconds(), TimeUnit.SECONDS);
+    }
+
+    public BulkProcessor add(IndexRequest request) {
+        internalAdd(request);
+        return this;
+    }
+
+    public BulkProcessor add(UpdateRequest request) {
+        internalAdd(request);
+        return this;
+    }
+
+    private void internalAdd(Object request) {
+        requireNonNull(request, "request");
+        requests.add(request);
+        flushIfNeeded();
+    }
+
+    @SneakyThrows
+    private void flushIfNeeded() {
+        if (requests.size() >= bulkActions) {
+            flush();
+        }
+    }
+
+    void flush() {
+        if (requests.isEmpty()) {
+            return;
+        }
+
+        try {
+            semaphore.acquire();
+        } catch (InterruptedException e) {
+            log.error("Interrupted when trying to get semaphore to execute bulk requests", e);
+            return;
+        }
+
+        final List<Object> batch = new ArrayList<>(requests.size());
+        requests.drainTo(batch);
+
+        final CompletableFuture<Void> flush = doFlush(batch);
+        flush.whenComplete((ignored1, ignored2) -> semaphore.release());
+        flush.join();
+    }
+
+    private CompletableFuture<Void> doFlush(final List<Object> batch) {
+        log.debug("Executing bulk with {} requests", batch.size());
+
+        final CompletableFuture<Void> future = es.get().version().thenCompose(v -> {
+            try {
+                final RequestFactory rf = v.requestFactory();
+                final List<byte[]> bs = new ArrayList<>();
+                for (final Object request : batch) {
+                    bs.add(v.codec().encode(request));
+                    bs.add("\n".getBytes());
+                }
+                final ByteBuf content = Unpooled.wrappedBuffer(bs.toArray(new byte[0][]));
+                return es.get().client().execute(rf.bulk().bulk(content))
+                         .aggregate().thenAccept(response -> {
+                        final HttpStatus status = response.status();
+                        if (status != HttpStatus.OK) {
+                            throw new RuntimeException(response.contentUtf8());
+                        }
+                    });
+            } catch (Exception e) {
+                return Exceptions.throwUnsafely(e);
+            }
+        });
+        future.whenComplete((ignored, exception) -> {
+            if (exception != null) {
+                log.error("Failed to execute requests in bulk", exception);
+            } else {
+                log.debug("Succeeded to execute {} requests in bulk", batch.size());
+            }
+        });
+        return future;
+    }
+}
diff --git a/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/bulk/BulkProcessorBuilder.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/bulk/BulkProcessorBuilder.java
new file mode 100644
index 0000000..3fbde7a
--- /dev/null
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/bulk/BulkProcessorBuilder.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.library.elasticsearch.bulk;
+
+import java.time.Duration;
+import java.util.concurrent.atomic.AtomicReference;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.skywalking.library.elasticsearch.ElasticSearch;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.Objects.requireNonNull;
+
+@Slf4j
+@RequiredArgsConstructor
+public final class BulkProcessorBuilder {
+    private int bulkActions = -1;
+    private Duration flushInterval;
+    private int concurrentRequests = 2;
+
+    public BulkProcessorBuilder bulkActions(int bulkActions) {
+        checkArgument(bulkActions > 0, "bulkActions must be positive");
+        this.bulkActions = bulkActions;
+        return this;
+    }
+
+    public BulkProcessorBuilder flushInterval(Duration flushInterval) {
+        this.flushInterval = requireNonNull(flushInterval, "flushInterval");
+        return this;
+    }
+
+    public BulkProcessorBuilder concurrentRequests(int concurrentRequests) {
+        checkArgument(concurrentRequests >= 0, "concurrentRequests must be >= 0");
+        this.concurrentRequests = concurrentRequests;
+        return this;
+    }
+
+    public BulkProcessor build(AtomicReference<ElasticSearch> es) {
+        return new BulkProcessor(
+            es, bulkActions, flushInterval, concurrentRequests);
+    }
+}
diff --git a/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/client/AliasClient.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/client/AliasClient.java
new file mode 100644
index 0000000..1809ef4
--- /dev/null
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/client/AliasClient.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.library.elasticsearch.client;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.linecorp.armeria.client.WebClient;
+import com.linecorp.armeria.common.HttpData;
+import com.linecorp.armeria.common.HttpStatus;
+import com.linecorp.armeria.common.util.Exceptions;
+import java.io.InputStream;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.skywalking.library.elasticsearch.ElasticSearchVersion;
+import org.apache.skywalking.library.elasticsearch.response.Index;
+
+@Slf4j
+@RequiredArgsConstructor
+public final class AliasClient {
+    private final CompletableFuture<ElasticSearchVersion> version;
+
+    private final WebClient client;
+
+    @SneakyThrows
+    public Map<String, Index> indices(String name) {
+        final CompletableFuture<Map<String, Index>> future =
+            version.thenCompose(
+                v -> client.execute(v.requestFactory().alias().indices(name))
+                           .aggregate().thenApply(response -> {
+                        final HttpStatus status = response.status();
+                        if (status != HttpStatus.OK) {
+                            throw new RuntimeException(response.contentUtf8());
+                        }
+
+                        try (final HttpData content = response.content();
+                             final InputStream is = content.toInputStream()) {
+                            return v.codec().decode(is, new TypeReference<Map<String, Index>>() {
+                            });
+                        } catch (Exception e) {
+                            return Exceptions.throwUnsafely(e);
+                        }
+                    }));
+        future.whenComplete((result, exception) -> {
+            if (exception != null) {
+                log.error("Failed to get indices by alias {}.", name, exception);
+                return;
+            }
+            if (log.isDebugEnabled()) {
+                log.debug("Indices by alias {}: {}", name, result);
+            }
+        });
+        return future.get();
+    }
+}
diff --git a/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/client/DocumentClient.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/client/DocumentClient.java
new file mode 100644
index 0000000..d28ff55
--- /dev/null
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/client/DocumentClient.java
@@ -0,0 +1,157 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.library.elasticsearch.client;
+
+import com.linecorp.armeria.client.WebClient;
+import com.linecorp.armeria.common.HttpData;
+import com.linecorp.armeria.common.HttpStatus;
+import com.linecorp.armeria.common.util.Exceptions;
+import java.io.InputStream;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.skywalking.library.elasticsearch.ElasticSearchVersion;
+import org.apache.skywalking.library.elasticsearch.requests.IndexRequest;
+import org.apache.skywalking.library.elasticsearch.requests.UpdateRequest;
+import org.apache.skywalking.library.elasticsearch.response.Document;
+import org.apache.skywalking.library.elasticsearch.response.Documents;
+
+@Slf4j
+@RequiredArgsConstructor
+public final class DocumentClient {
+    private final CompletableFuture<ElasticSearchVersion> version;
+
+    private final WebClient client;
+
+    @SneakyThrows
+    public boolean exists(String index, String type, String id) {
+        return version.thenCompose(
+            v -> client.execute(v.requestFactory().document().exist(index, type, id))
+                       .aggregate().thenApply(response -> response.status() == HttpStatus.OK)
+                       .exceptionally(e -> {
+                           log.error("Failed to check whether document exists", e);
+                           return false;
+                       })).get();
+    }
+
+    @SneakyThrows
+    public Optional<Document> get(String index, String type, String id) {
+        final CompletableFuture<Optional<Document>> future = version.thenCompose(
+            v -> client.execute(v.requestFactory().document().get(index, type, id))
+                       .aggregate().thenApply(response -> {
+                    if (response.status() != HttpStatus.OK) {
+                        throw new RuntimeException(response.contentUtf8());
+                    }
+
+                    try (final HttpData content = response.content();
+                         final InputStream is = content.toInputStream()) {
+                        final Document document = v.codec().decode(is, Document.class);
+                        if (!document.isFound()) {
+                            return Optional.empty();
+                        }
+                        return Optional.of(document);
+                    } catch (Exception e) {
+                        return Exceptions.throwUnsafely(e);
+                    }
+                }));
+        future.whenComplete((result, exception) -> {
+            if (exception != null) {
+                log.error("Failed to get doc by id {} in index {}", id, index, exception);
+                return;
+            }
+            if (log.isDebugEnabled()) {
+                log.debug("Doc by id {} in index {}: {}", id, index, result);
+            }
+        });
+        return future.get();
+    }
+
+    @SneakyThrows
+    public Optional<Documents> mget(String index, String type, Iterable<String> ids) {
+        final CompletableFuture<Optional<Documents>> future =
+            version.thenCompose(
+                v -> client.execute(v.requestFactory().document().mget(index, type, ids))
+                           .aggregate().thenApply(response -> {
+                        if (response.status() != HttpStatus.OK) {
+                            throw new RuntimeException(response.contentUtf8());
+                        }
+
+                        try (final HttpData content = response.content();
+                             final InputStream is = content.toInputStream()) {
+                            return Optional.of(v.codec().decode(is, Documents.class));
+                        } catch (Exception e) {
+                            return Exceptions.throwUnsafely(e);
+                        }
+                    }));
+        future.whenComplete((result, exception) -> {
+            if (exception != null) {
+                log.error("Failed to get by ids, {} {}", index, ids, exception);
+            } else if (log.isDebugEnabled()) {
+                log.debug("Succeeded to get docs by ids: {} {} {}", index, ids, result);
+            }
+        });
+        return future.get();
+    }
+
+    @SneakyThrows
+    public void index(IndexRequest request, Map<String, Object> params) {
+        final CompletableFuture<Void> future = version.thenCompose(
+            v -> client.execute(v.requestFactory().document().index(request, params))
+                       .aggregate().thenAccept(response -> {
+                    final HttpStatus status = response.status();
+                    if (status != HttpStatus.CREATED && status != HttpStatus.OK) {
+                        throw new RuntimeException(response.contentUtf8());
+                    }
+                }));
+        future.whenComplete((result, exception) -> {
+            if (exception != null) {
+                log.error("Failed to index doc: {}, params: {}", request, params, exception);
+                return;
+            }
+            if (log.isDebugEnabled()) {
+                log.debug("Succeeded indexing doc: {}, params: {}", request, params);
+            }
+        });
+        future.join();
+    }
+
+    @SneakyThrows
+    public void update(UpdateRequest request, Map<String, Object> params) {
+        final CompletableFuture<Void> future = version.thenCompose(
+            v -> client.execute(v.requestFactory().document().update(request, params))
+                       .aggregate().thenAccept(response -> {
+                    final HttpStatus status = response.status();
+                    if (status != HttpStatus.OK) {
+                        throw new RuntimeException(response.contentUtf8());
+                    }
+                }));
+        future.whenComplete((result, exception) -> {
+            if (exception != null) {
+                log.error("Failed to update doc: {}, params: {}", request, params, exception);
+                return;
+            }
+            if (log.isDebugEnabled()) {
+                log.debug("Succeeded updating doc: {}, params: {}", request, params);
+            }
+        });
+        future.join();
+    }
+}
diff --git a/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/client/IndexClient.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/client/IndexClient.java
new file mode 100644
index 0000000..f585a70
--- /dev/null
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/client/IndexClient.java
@@ -0,0 +1,167 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.library.elasticsearch.client;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.linecorp.armeria.client.WebClient;
+import com.linecorp.armeria.common.HttpData;
+import com.linecorp.armeria.common.HttpStatus;
+import com.linecorp.armeria.common.util.Exceptions;
+import java.io.InputStream;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.skywalking.library.elasticsearch.ElasticSearchVersion;
+import org.apache.skywalking.library.elasticsearch.response.Index;
+import org.apache.skywalking.library.elasticsearch.response.Mappings;
+
+@Slf4j
+@RequiredArgsConstructor
+public final class IndexClient {
+    private final CompletableFuture<ElasticSearchVersion> version;
+
+    private final WebClient client;
+
+    @SneakyThrows
+    public boolean exists(String name) {
+        final CompletableFuture<Boolean> future = version.thenCompose(
+            v -> client.execute(v.requestFactory().index().exists(name))
+                       .aggregate().thenApply(response -> {
+                    if (response.status() == HttpStatus.OK) {
+                        return true;
+                    }
+                    if (response.status() == HttpStatus.NOT_FOUND) {
+                        return false;
+                    }
+                    throw new RuntimeException(response.contentUtf8());
+                }));
+        future.whenComplete((result, exception) -> {
+            if (exception != null) {
+                log.error("Failed to check whether index {} exist", name, exception);
+                return;
+            }
+            if (log.isDebugEnabled()) {
+                log.debug("Succeeded to check whether index {} exist: {}", name, result);
+            }
+        });
+        return future.get();
+    }
+
+    @SneakyThrows
+    public Optional<Index> get(String name) {
+        final TypeReference<Map<String, Index>> type =
+            new TypeReference<Map<String, Index>>() {
+            };
+        final CompletableFuture<Optional<Index>> future = version.thenCompose(
+            v -> client.execute(v.requestFactory().index().get(name))
+                       .aggregate().thenApply(response -> {
+                    final HttpStatus status = response.status();
+                    if (status == HttpStatus.NOT_FOUND) {
+                        return Optional.empty();
+                    }
+
+                    try (final HttpData content = response.content();
+                         final InputStream is = content.toInputStream()) {
+                        final Map<String, Index> indices = v.codec().decode(is, type);
+                        return Optional.ofNullable(indices.get(name));
+                    } catch (Exception e) {
+                        return Exceptions.throwUnsafely(e);
+                    }
+                }));
+        future.whenComplete((result, exception) -> {
+            if (exception != null) {
+                log.error("Failed to get index: {}", name, exception);
+                return;
+            }
+            if (log.isDebugEnabled()) {
+                log.debug("Succeeded to get index, {}: {}", name, result);
+            }
+        });
+        return future.get();
+    }
+
+    @SneakyThrows
+    public boolean create(String name,
+                          Mappings mappings,
+                          Map<String, ?> settings) {
+        final CompletableFuture<Boolean> future = version.thenCompose(
+            v -> client.execute(v.requestFactory().index().create(name, mappings, settings))
+                       .aggregate().thenApply(response -> {
+                    if (response.status() == HttpStatus.OK) {
+                        return true;
+                    }
+                    throw new RuntimeException(response.contentUtf8());
+                }));
+        future.whenComplete((result, exception) -> {
+            if (exception != null) {
+                log.error("Failed to create index", exception);
+                return;
+            }
+            if (log.isDebugEnabled()) {
+                log.debug("Succeeded to create index {}, {}", name, result);
+            }
+        });
+        return future.get();
+    }
+
+    @SneakyThrows
+    public boolean delete(String name) {
+        final CompletableFuture<Boolean> future = version.thenCompose(
+            v -> client.execute(v.requestFactory().index().delete(name))
+                       .aggregate().thenApply(response -> {
+                    if (response.status() == HttpStatus.OK) {
+                        return true;
+                    }
+                    throw new RuntimeException(response.contentUtf8());
+                }));
+        future.whenComplete((deleted, exception) -> {
+            if (exception != null) {
+                log.error("Failed to delete index. {}", name, exception);
+                return;
+            }
+            log.debug("Delete index {} result: {}", name, deleted);
+        });
+        return future.get();
+    }
+
+    @SneakyThrows
+    public boolean putMapping(String name, String type, Mappings mapping) {
+        final CompletableFuture<Boolean> future = version.thenCompose(
+            v -> client.execute(v.requestFactory().index().putMapping(name, type, mapping))
+                       .aggregate().thenApply(response -> {
+                    if (response.status() == HttpStatus.OK) {
+                        return true;
+                    }
+                    throw new RuntimeException(response.contentUtf8());
+                }));
+        future.whenComplete((result, exception) -> {
+            if (exception != null) {
+                log.error(
+                    "Failed to update index mapping {}, mapping: {}", name, mapping, exception);
+                return;
+            }
+            if (log.isDebugEnabled()) {
+                log.debug("Succeeded to update index mapping {}, {}", name, result);
+            }
+        });
+        return future.get();
+    }
+}
diff --git a/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/client/SearchClient.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/client/SearchClient.java
new file mode 100644
index 0000000..d585a2f
--- /dev/null
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/client/SearchClient.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.library.elasticsearch.client;
+
+import com.linecorp.armeria.client.WebClient;
+import com.linecorp.armeria.common.HttpData;
+import com.linecorp.armeria.common.HttpStatus;
+import com.linecorp.armeria.common.util.Exceptions;
+import java.io.InputStream;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.skywalking.library.elasticsearch.ElasticSearchVersion;
+import org.apache.skywalking.library.elasticsearch.requests.search.Search;
+import org.apache.skywalking.library.elasticsearch.response.search.SearchResponse;
+
+@Slf4j
+@RequiredArgsConstructor
+public final class SearchClient {
+    private final CompletableFuture<ElasticSearchVersion> version;
+
+    private final WebClient client;
+
+    @SneakyThrows
+    public SearchResponse search(Search criteria,
+                                 Map<String, ?> params,
+                                 String... index) {
+        final CompletableFuture<SearchResponse> future =
+            version.thenCompose(
+                v -> client.execute(v.requestFactory().search().search(criteria, params, index))
+                           .aggregate().thenApply(response -> {
+                        if (response.status() != HttpStatus.OK) {
+                            throw new RuntimeException(response.contentUtf8());
+                        }
+
+                        try (final HttpData content = response.content();
+                             final InputStream is = content.toInputStream()) {
+                            return v.codec().decode(is, SearchResponse.class);
+                        } catch (Exception e) {
+                            return Exceptions.throwUnsafely(e);
+                        }
+                    }));
+        future.whenComplete((result, exception) -> {
+            if (exception != null) {
+                log.error(
+                    "Failed to search, request {}, params {}, index {}",
+                    criteria, params, index,
+                    exception
+                );
+                return;
+            }
+            if (log.isDebugEnabled()) {
+                log.debug("Succeeded to search index {}, {}", index, result);
+            }
+        });
+        return future.get();
+    }
+}
diff --git a/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/client/TemplateClient.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/client/TemplateClient.java
new file mode 100644
index 0000000..07489eb
--- /dev/null
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/client/TemplateClient.java
@@ -0,0 +1,149 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.library.elasticsearch.client;
+
+import com.linecorp.armeria.client.WebClient;
+import com.linecorp.armeria.common.HttpData;
+import com.linecorp.armeria.common.HttpStatus;
+import com.linecorp.armeria.common.util.Exceptions;
+import java.io.InputStream;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.skywalking.library.elasticsearch.ElasticSearchVersion;
+import org.apache.skywalking.library.elasticsearch.response.IndexTemplate;
+import org.apache.skywalking.library.elasticsearch.response.IndexTemplates;
+import org.apache.skywalking.library.elasticsearch.response.Mappings;
+
+@Slf4j
+@RequiredArgsConstructor
+public final class TemplateClient {
+    private final CompletableFuture<ElasticSearchVersion> version;
+
+    private final WebClient client;
+
+    @SneakyThrows
+    public boolean exists(String name) {
+        final CompletableFuture<Boolean> future = version.thenCompose(
+            v -> client.execute(v.requestFactory().template().exists(name))
+                       .aggregate().thenApply(response -> {
+                    final HttpStatus status = response.status();
+                    if (status == HttpStatus.OK) {
+                        return true;
+                    } else if (status == HttpStatus.NOT_FOUND) {
+                        return false;
+                    }
+                    throw new RuntimeException(response.contentUtf8());
+                }));
+        future.whenComplete((result, exception) -> {
+            if (exception != null) {
+                log.error("Failed to check whether template {} exists", name, exception);
+                return;
+            }
+            if (log.isDebugEnabled()) {
+                log.debug("Succeeded to check whether template {} exists {}", name, result);
+            }
+        });
+        return future.get();
+    }
+
+    @SneakyThrows
+    public Optional<IndexTemplate> get(String name) {
+        final CompletableFuture<Optional<IndexTemplate>> future =
+            version.thenCompose(
+                v -> client.execute(v.requestFactory().template().get(name))
+                           .aggregate().thenApply(response -> {
+                        final HttpStatus status = response.status();
+                        if (status == HttpStatus.NOT_FOUND) {
+                            return Optional.empty();
+                        }
+                        if (status != HttpStatus.OK) {
+                            throw new RuntimeException(response.contentUtf8());
+                        }
+
+                        try (final HttpData content = response.content();
+                             final InputStream is = content.toInputStream()) {
+                            final IndexTemplates templates =
+                                v.codec().decode(is, IndexTemplates.class);
+                            return templates.get(name);
+                        } catch (Exception e) {
+                            return Exceptions.throwUnsafely(e);
+                        }
+                    }));
+        future.whenComplete((result, exception) -> {
+            if (exception != null) {
+                log.error("Failed to get index template {}", name, exception);
+                return;
+            }
+            if (log.isDebugEnabled()) {
+                log.debug("Succeeded to get index template {}, {}", name, result);
+            }
+        });
+        return future.get();
+    }
+
+    @SneakyThrows
+    public boolean delete(String name) {
+        final CompletableFuture<Boolean> future = version.thenCompose(
+            v -> client.execute(v.requestFactory().template().delete(name))
+                       .aggregate().thenApply(response -> {
+                    if (response.status() == HttpStatus.OK) {
+                        return true;
+                    }
+                    throw new RuntimeException(response.contentUtf8());
+                }));
+        future.whenComplete((result, exception) -> {
+            if (exception != null) {
+                log.error("Failed to delete index template {}", name, exception);
+                return;
+            }
+            if (log.isDebugEnabled()) {
+                log.debug("Succeeded to delete index template {}, {}", name, result);
+            }
+        });
+        return future.get();
+    }
+
+    @SneakyThrows
+    public boolean createOrUpdate(String name, Map<String, Object> settings,
+                                  Mappings mappings, int order) {
+        final CompletableFuture<Boolean> future = version.thenCompose(
+            v -> client.execute(v.requestFactory().template()
+                                 .createOrUpdate(name, settings, mappings, order))
+                       .aggregate().thenApply(response -> {
+                    if (response.status() == HttpStatus.OK) {
+                        return true;
+                    }
+                    throw new RuntimeException(response.contentUtf8());
+                }));
+        future.whenComplete((result, exception) -> {
+            if (exception != null) {
+                log.error("Failed to create / update index template {}", name, exception);
+                return;
+            }
+            if (log.isDebugEnabled()) {
+                log.debug("Succeeded to create / update index template {}, {}", name, result);
+            }
+        });
+        return future.get();
+    }
+
+}
diff --git a/oap-server/server-library/library-client/src/test/java/org/apache/skywalking/oap/server/library/client/elasticsearch/ITElasticSearchClientOfNamespace.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/IndexRequest.java
similarity index 70%
copy from oap-server/server-library/library-client/src/test/java/org/apache/skywalking/oap/server/library/client/elasticsearch/ITElasticSearchClientOfNamespace.java
copy to oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/IndexRequest.java
index 7960299..818c3b9 100644
--- a/oap-server/server-library/library-client/src/test/java/org/apache/skywalking/oap/server/library/client/elasticsearch/ITElasticSearchClientOfNamespace.java
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/IndexRequest.java
@@ -13,14 +13,21 @@
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
- *
  */
 
-package org.apache.skywalking.oap.server.library.client.elasticsearch;
+package org.apache.skywalking.library.elasticsearch.requests;
 
-public class ITElasticSearchClientOfNamespace extends ITElasticSearchClient {
+import java.util.Map;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.Setter;
 
-    public ITElasticSearchClientOfNamespace() {
-        super("test");
-    }
+@Getter
+@Setter
+@Builder
+public final class IndexRequest {
+    private final String index;
+    private final String type;
+    private final String id;
+    private final Map<String, ?> doc;
 }
diff --git a/oap-server/server-library/library-client/src/test/java/org/apache/skywalking/oap/server/library/client/elasticsearch/ITElasticSearchClientOfNamespace.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/UpdateRequest.java
similarity index 69%
copy from oap-server/server-library/library-client/src/test/java/org/apache/skywalking/oap/server/library/client/elasticsearch/ITElasticSearchClientOfNamespace.java
copy to oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/UpdateRequest.java
index 7960299..37bed7e 100644
--- a/oap-server/server-library/library-client/src/test/java/org/apache/skywalking/oap/server/library/client/elasticsearch/ITElasticSearchClientOfNamespace.java
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/UpdateRequest.java
@@ -13,14 +13,21 @@
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
- *
  */
 
-package org.apache.skywalking.oap.server.library.client.elasticsearch;
+package org.apache.skywalking.library.elasticsearch.requests;
 
-public class ITElasticSearchClientOfNamespace extends ITElasticSearchClient {
+import java.util.Map;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.Setter;
 
-    public ITElasticSearchClientOfNamespace() {
-        super("test");
-    }
+@Getter
+@Setter
+@Builder
+public final class UpdateRequest {
+    private final String index;
+    private final String type;
+    private final String id;
+    private final Map<String, Object> doc;
 }
diff --git a/oap-server/server-storage-plugin/storage-elasticsearch7-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch7/StorageModuleElasticsearch7Config.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/AliasFactory.java
similarity index 74%
rename from oap-server/server-storage-plugin/storage-elasticsearch7-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch7/StorageModuleElasticsearch7Config.java
rename to oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/AliasFactory.java
index 2b3ba51..6ccf6b8 100644
--- a/oap-server/server-storage-plugin/storage-elasticsearch7-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch7/StorageModuleElasticsearch7Config.java
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/AliasFactory.java
@@ -13,12 +13,15 @@
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
- *
  */
 
-package org.apache.skywalking.oap.server.storage.plugin.elasticsearch7;
+package org.apache.skywalking.library.elasticsearch.requests.factory;
 
-import org.apache.skywalking.oap.server.storage.plugin.elasticsearch.StorageModuleElasticsearchConfig;
+import com.linecorp.armeria.common.HttpRequest;
 
-public class StorageModuleElasticsearch7Config extends StorageModuleElasticsearchConfig {
+public interface AliasFactory {
+    /**
+     * Returns a request to list all indices behind the {@code alias}.
+     */
+    HttpRequest indices(String alias);
 }
diff --git a/oap-server/server-library/library-client/src/main/java/org/apache/skywalking/oap/server/library/client/ClientException.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/BulkFactory.java
similarity index 70%
rename from oap-server/server-library/library-client/src/main/java/org/apache/skywalking/oap/server/library/client/ClientException.java
rename to oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/BulkFactory.java
index 6374b9f..9b64ab1 100644
--- a/oap-server/server-library/library-client/src/main/java/org/apache/skywalking/oap/server/library/client/ClientException.java
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/BulkFactory.java
@@ -13,17 +13,16 @@
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
- *
  */
 
-package org.apache.skywalking.oap.server.library.client;
+package org.apache.skywalking.library.elasticsearch.requests.factory;
 
-public abstract class ClientException extends Exception {
-    public ClientException(String message) {
-        super(message);
-    }
+import com.linecorp.armeria.common.HttpRequest;
+import io.netty.buffer.ByteBuf;
 
-    public ClientException(String message, Throwable cause) {
-        super(message, cause);
-    }
+public interface BulkFactory {
+    /**
+     * Returns a request to perform multiple indexing or delete operations in a single API call.
+     */
+    HttpRequest bulk(ByteBuf content);
 }
diff --git a/oap-server/server-starter-es7/src/main/java/org/apache/skywalking/oap/server/starter/OAPServerStartUp.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/Codec.java
similarity index 58%
rename from oap-server/server-starter-es7/src/main/java/org/apache/skywalking/oap/server/starter/OAPServerStartUp.java
rename to oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/Codec.java
index f91f5f8..e69678b 100644
--- a/oap-server/server-starter-es7/src/main/java/org/apache/skywalking/oap/server/starter/OAPServerStartUp.java
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/Codec.java
@@ -13,17 +13,25 @@
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
- *
  */
 
-package org.apache.skywalking.oap.server.starter;
+package org.apache.skywalking.library.elasticsearch.requests.factory;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import java.io.InputStream;
 
 /**
- * OAP starter specific for the ES7 storage. This includes the same code of OAPServerStartUp in the `server-starter`
- * module.
+ * Responsible to encode requests and decode responses.
  */
-public class OAPServerStartUp {
-    public static void main(String[] args) {
-        OAPServerBootstrap.start();
+public interface Codec {
+    byte[] encode(Object request) throws Exception;
+
+    <T> T decode(InputStream inputStream, TypeReference<T> type) throws Exception;
+
+    <T> T decode(InputStream inputStream, Class<T> type) throws Exception;
+
+    default <T> T decode(InputStream inputStream) throws Exception {
+        return decode(inputStream, new TypeReference<T>() {
+        });
     }
 }
diff --git a/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/DocumentFactory.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/DocumentFactory.java
new file mode 100644
index 0000000..ae77b97
--- /dev/null
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/DocumentFactory.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.library.elasticsearch.requests.factory;
+
+import com.linecorp.armeria.common.HttpRequest;
+import java.util.Map;
+import org.apache.skywalking.library.elasticsearch.requests.IndexRequest;
+import org.apache.skywalking.library.elasticsearch.requests.UpdateRequest;
+import org.apache.skywalking.library.elasticsearch.requests.search.Query;
+
+public interface DocumentFactory {
+    /**
+     * Returns a request to check whether the document exists in the {@code index} or not.
+     */
+    HttpRequest exist(String index, String type, String id);
+
+    /**
+     * Returns a request to get a document of {@code id} in {@code index}.
+     */
+    HttpRequest get(String index, String type, String id);
+
+    /**
+     * Returns a request to get multiple documents of {@code ids} in {@code index}.
+     */
+    HttpRequest mget(String index, String type, Iterable<String> ids);
+
+    /**
+     * Returns a request to index a document with {@link IndexRequest}.
+     */
+    HttpRequest index(IndexRequest request, Map<String, ?> params);
+
+    /**
+     * Returns a request to update a document with {@link UpdateRequest}.
+     */
+    HttpRequest update(UpdateRequest request, Map<String, ?> params);
+
+    /**
+     * Returns a request to delete documents matching the given {@code query} in {@code index}.
+     */
+    HttpRequest delete(String index, String type, Query query,
+                       Map<String, ?> params);
+}
diff --git a/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/IndexFactory.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/IndexFactory.java
new file mode 100644
index 0000000..955e447
--- /dev/null
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/IndexFactory.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.library.elasticsearch.requests.factory;
+
+import com.linecorp.armeria.common.HttpRequest;
+import java.util.Map;
+import org.apache.skywalking.library.elasticsearch.response.Mappings;
+
+public interface IndexFactory {
+    /**
+     * Returns a request to check whether the {@code index} exists or not.
+     */
+    HttpRequest exists(String index);
+
+    /**
+     * Returns a request to get an index of name {@code index}.
+     */
+    HttpRequest get(String index);
+
+    /**
+     * Returns a request to create an index of name {@code index}.
+     */
+    HttpRequest create(String index,
+                       Mappings mappings,
+                       Map<String, ?> settings);
+
+    /**
+     * Returns a request to delete an index of name {@code index}.
+     */
+    HttpRequest delete(String index);
+
+    /**
+     * Returns a request to update the {@code mappings} of an index of name {@code index}.
+     */
+    HttpRequest putMapping(String index, String type, Mappings mappings);
+}
diff --git a/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/RequestFactory.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/RequestFactory.java
new file mode 100644
index 0000000..775d2eb
--- /dev/null
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/RequestFactory.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.library.elasticsearch.requests.factory;
+
+public interface RequestFactory {
+    /**
+     * Returns a {@link TemplateFactory} that is dedicated to compose template-related requests.
+     *
+     * @see TemplateFactory
+     */
+    TemplateFactory template();
+
+    /**
+     * Returns a {@link IndexFactory} that is dedicated to compose index-related requests.
+     *
+     * @see IndexFactory
+     */
+    IndexFactory index();
+
+    /**
+     * Returns a {@link AliasFactory} that is dedicated to compose alias-related requests.
+     *
+     * @see AliasFactory
+     */
+    AliasFactory alias();
+
+    /**
+     * Returns a {@link DocumentFactory} that is dedicated to compose document-related requests.
+     *
+     * @see DocumentFactory
+     */
+    DocumentFactory document();
+
+    /**
+     * Returns a {@link SearchFactory} that is dedicated to compose searching-related requests.
+     *
+     * @see DocumentFactory
+     */
+    SearchFactory search();
+
+    /**
+     * Returns a {@link SearchFactory} that is dedicated to compose bulk-related requests.
+     *
+     * @see DocumentFactory
+     */
+    BulkFactory bulk();
+}
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/type/StorageDataComplexObject.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/SearchFactory.java
similarity index 60%
copy from oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/type/StorageDataComplexObject.java
copy to oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/SearchFactory.java
index ebad19e..3824a4b 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/type/StorageDataComplexObject.java
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/SearchFactory.java
@@ -13,27 +13,24 @@
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
- *
  */
 
-package org.apache.skywalking.oap.server.core.storage.type;
+package org.apache.skywalking.library.elasticsearch.requests.factory;
 
-/**
- * StorageDataComplexObject implementation supports String-Object interconversion.
- */
-public interface StorageDataComplexObject<T> {
-    /**
-     * @return string representing this object.
-     */
-    String toStorageData();
+import com.linecorp.armeria.common.HttpRequest;
+import java.util.Map;
+import org.apache.skywalking.library.elasticsearch.requests.search.Search;
 
+public interface SearchFactory {
     /**
-     * Initialize this object based on the given string data.
+     * Returns a request to search documents.
      */
-    void toObject(String data);
+    HttpRequest search(Search search, Map<String, ?> queryParams, String... index);
 
     /**
-     * Initialize the object based on the given source.
+     * Returns a request to search documents.
      */
-    void copyFrom(T source);
+    default HttpRequest search(Search search, String... index) {
+        return search(search, null, index);
+    }
 }
diff --git a/oap-server/server-library/library-client/src/main/java/org/apache/skywalking/oap/server/library/client/elasticsearch/IndexNameConverter.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/TemplateFactory.java
similarity index 51%
rename from oap-server/server-library/library-client/src/main/java/org/apache/skywalking/oap/server/library/client/elasticsearch/IndexNameConverter.java
rename to oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/TemplateFactory.java
index faeca02..3fb7a66 100644
--- a/oap-server/server-library/library-client/src/main/java/org/apache/skywalking/oap/server/library/client/elasticsearch/IndexNameConverter.java
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/TemplateFactory.java
@@ -13,14 +13,34 @@
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
- *
  */
 
-package org.apache.skywalking.oap.server.library.client.elasticsearch;
+package org.apache.skywalking.library.elasticsearch.requests.factory;
 
-/**
- * Implementation supports the ElasticSearch index name converting.
- */
-public interface IndexNameConverter {
-    String convert(String name);
+import com.linecorp.armeria.common.HttpRequest;
+import java.util.Map;
+import org.apache.skywalking.library.elasticsearch.response.Mappings;
+
+public interface TemplateFactory {
+    /**
+     * Returns a request to check whether the template exists or not.
+     */
+    HttpRequest exists(String name);
+
+    /**
+     * Returns a request to get a template of {@code name}.
+     */
+    HttpRequest get(String name);
+
+    /**
+     * Returns a request to delete a template of {@code name}.
+     */
+    HttpRequest delete(String name);
+
+    /**
+     * Returns a request to create or update a template of {@code name} with the given {@code
+     * settings}, {@code mapping} and {@code order}.
+     */
+    HttpRequest createOrUpdate(String name, Map<String, ?> settings,
+                               Mappings mappings, int order);
 }
diff --git a/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/common/CommonAliasFactory.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/common/CommonAliasFactory.java
new file mode 100644
index 0000000..b8c218a
--- /dev/null
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/common/CommonAliasFactory.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.library.elasticsearch.requests.factory.common;
+
+import com.google.common.base.Strings;
+import com.linecorp.armeria.common.HttpRequest;
+import lombok.RequiredArgsConstructor;
+import org.apache.skywalking.library.elasticsearch.ElasticSearchVersion;
+import org.apache.skywalking.library.elasticsearch.requests.factory.AliasFactory;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+@RequiredArgsConstructor
+public final class CommonAliasFactory implements AliasFactory {
+    private final ElasticSearchVersion version;
+
+    @Override
+    public HttpRequest indices(String alias) {
+        checkArgument(!Strings.isNullOrEmpty(alias), "alias cannot be null or empty");
+
+        return HttpRequest.builder()
+                          .get("/_alias/{name}")
+                          .pathParam("name", alias)
+                          .build();
+    }
+}
diff --git a/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/common/CommonBulkFactory.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/common/CommonBulkFactory.java
new file mode 100644
index 0000000..733ec5f
--- /dev/null
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/common/CommonBulkFactory.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.library.elasticsearch.requests.factory.common;
+
+import com.linecorp.armeria.common.HttpData;
+import com.linecorp.armeria.common.HttpRequest;
+import com.linecorp.armeria.common.MediaType;
+import io.netty.buffer.ByteBuf;
+import java.nio.charset.StandardCharsets;
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.skywalking.library.elasticsearch.ElasticSearchVersion;
+import org.apache.skywalking.library.elasticsearch.requests.factory.BulkFactory;
+
+import static java.util.Objects.requireNonNull;
+
+@Slf4j
+@RequiredArgsConstructor
+public final class CommonBulkFactory implements BulkFactory {
+    private final ElasticSearchVersion version;
+
+    @SneakyThrows
+    @Override
+    public HttpRequest bulk(ByteBuf content) {
+        requireNonNull(content, "content");
+
+        if (log.isDebugEnabled()) {
+            log.debug("Bulk requests: {}", content.toString(StandardCharsets.UTF_8));
+        }
+
+        return HttpRequest.builder()
+                          .post("/_bulk")
+                          .content(MediaType.JSON, HttpData.wrap(content))
+                          .build();
+    }
+}
diff --git a/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/common/CommonSearchFactory.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/common/CommonSearchFactory.java
new file mode 100644
index 0000000..2b4ff04
--- /dev/null
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/common/CommonSearchFactory.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.library.elasticsearch.requests.factory.common;
+
+import com.linecorp.armeria.common.HttpRequest;
+import com.linecorp.armeria.common.HttpRequestBuilder;
+import com.linecorp.armeria.common.MediaType;
+import java.util.Map;
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.skywalking.library.elasticsearch.ElasticSearchVersion;
+import org.apache.skywalking.library.elasticsearch.requests.factory.SearchFactory;
+import org.apache.skywalking.library.elasticsearch.requests.search.Search;
+
+@Slf4j
+@RequiredArgsConstructor
+public final class CommonSearchFactory implements SearchFactory {
+    private final ElasticSearchVersion version;
+
+    @SneakyThrows
+    @Override
+    public HttpRequest search(Search search,
+                              Map<String, ?> queryParams,
+                              String... indices) {
+        final HttpRequestBuilder builder = HttpRequest.builder();
+
+        if (indices == null || indices.length == 0) {
+            builder.get("/_search");
+        } else {
+            builder.get("/{indices}/_search")
+                   .pathParam("indices", String.join(",", indices));
+        }
+
+        if (queryParams != null && !queryParams.isEmpty()) {
+            queryParams.forEach(builder::queryParam);
+        }
+
+        final byte[] content = version.codec().encode(search);
+
+        if (log.isDebugEnabled()) {
+            log.debug("Search request: {}", new String(content));
+        }
+
+        return builder.content(MediaType.JSON, content)
+                      .build();
+    }
+}
diff --git a/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/v6/V6DocumentFactory.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/v6/V6DocumentFactory.java
new file mode 100644
index 0000000..bfb1880
--- /dev/null
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/v6/V6DocumentFactory.java
@@ -0,0 +1,174 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.library.elasticsearch.requests.factory.v6;
+
+import com.google.common.collect.ImmutableMap;
+import com.linecorp.armeria.common.HttpRequest;
+import com.linecorp.armeria.common.HttpRequestBuilder;
+import com.linecorp.armeria.common.MediaType;
+import java.util.Map;
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.skywalking.library.elasticsearch.ElasticSearchVersion;
+import org.apache.skywalking.library.elasticsearch.requests.IndexRequest;
+import org.apache.skywalking.library.elasticsearch.requests.UpdateRequest;
+import org.apache.skywalking.library.elasticsearch.requests.factory.DocumentFactory;
+import org.apache.skywalking.library.elasticsearch.requests.search.Query;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Strings.isNullOrEmpty;
+import static com.google.common.collect.Iterables.isEmpty;
+import static java.util.Objects.requireNonNull;
+
+@Slf4j
+@RequiredArgsConstructor
+final class V6DocumentFactory implements DocumentFactory {
+    private final ElasticSearchVersion version;
+
+    @Override
+    public HttpRequest exist(String index, String type, String id) {
+        checkArgument(!isNullOrEmpty(index), "index cannot be null or empty");
+        checkArgument(!isNullOrEmpty(type), "type cannot be null or empty");
+        checkArgument(!isNullOrEmpty(id), "id cannot be null or empty");
+
+        return HttpRequest.builder()
+                          .head("/{index}/{type}/{id}")
+                          .pathParam("index", index)
+                          .pathParam("type", type)
+                          .pathParam("id", id)
+                          .build();
+    }
+
+    @Override
+    public HttpRequest get(String index, String type, String id) {
+        checkArgument(!isNullOrEmpty(index), "index cannot be null or empty");
+        checkArgument(!isNullOrEmpty(type), "type cannot be null or empty");
+        checkArgument(!isNullOrEmpty(id), "id cannot be null or empty");
+
+        return HttpRequest.builder()
+                          .get("/{index}/{type}/{id}")
+                          .pathParam("index", index)
+                          .pathParam("type", type)
+                          .pathParam("id", id)
+                          .build();
+    }
+
+    @SneakyThrows
+    @Override
+    public HttpRequest mget(String index, String type, Iterable<String> ids) {
+        checkArgument(!isNullOrEmpty(index), "index cannot be null or empty");
+        checkArgument(!isNullOrEmpty(type), "type cannot be null or empty");
+        checkArgument(ids != null && !isEmpty(ids), "ids cannot be null or empty");
+
+        final Map<String, Iterable<String>> m = ImmutableMap.of("ids", ids);
+        final byte[] content = version.codec().encode(m);
+
+        if (log.isDebugEnabled()) {
+            log.debug("mget {} ids: {}", index, ids);
+        }
+
+        return HttpRequest.builder()
+                          .get("/{index}/{type}/_mget")
+                          .pathParam("index", index)
+                          .pathParam("type", type)
+                          .content(MediaType.JSON, content)
+                          .build();
+    }
+
+    @SneakyThrows
+    @Override
+    public HttpRequest index(IndexRequest request, Map<String, ?> params) {
+        requireNonNull(request, "request");
+
+        final String index = request.getIndex();
+        final String type = request.getType();
+        final String id = request.getId();
+        final Map<String, ?> doc = request.getDoc();
+
+        checkArgument(!isNullOrEmpty(index), "request.index cannot be null or empty");
+        checkArgument(!isNullOrEmpty(type), "request.type cannot be null or empty");
+        checkArgument(!isNullOrEmpty(id), "request.id cannot be null or empty");
+
+        final HttpRequestBuilder builder = HttpRequest.builder();
+        if (params != null) {
+            params.forEach(builder::queryParam);
+        }
+        final byte[] content = version.codec().encode(doc);
+
+        builder.put("/{index}/{type}/{id}")
+               .pathParam("index", index)
+               .pathParam("type", type)
+               .pathParam("id", id)
+               .content(MediaType.JSON, content);
+
+        return builder.build();
+    }
+
+    @SneakyThrows
+    @Override
+    public HttpRequest update(UpdateRequest request, Map<String, ?> params) {
+        requireNonNull(request, "request");
+
+        final String index = request.getIndex();
+        final String type = request.getType();
+        final String id = request.getId();
+        final Map<String, Object> doc = request.getDoc();
+
+        checkArgument(!isNullOrEmpty(index), "index cannot be null or empty");
+        checkArgument(!isNullOrEmpty(type), "type cannot be null or empty");
+        checkArgument(!isNullOrEmpty(id), "id cannot be null or empty");
+        checkArgument(doc != null && !isEmpty(doc.entrySet()), "doc cannot be null or empty");
+
+        final HttpRequestBuilder builder = HttpRequest.builder();
+        if (params != null) {
+            params.forEach(builder::queryParam);
+        }
+        final byte[] content = version.codec().encode(ImmutableMap.of("doc", doc));
+
+        builder.post("/{index}/{type}/{id}/_update")
+               .pathParam("index", index)
+               .pathParam("type", type)
+               .pathParam("id", id)
+               .content(MediaType.JSON, content);
+
+        return builder.build();
+    }
+
+    @SneakyThrows
+    @Override
+    public HttpRequest delete(String index, String type, Query query,
+                              Map<String, ?> params) {
+        checkArgument(!isNullOrEmpty(index), "index cannot be null or empty");
+        checkArgument(!isNullOrEmpty(type), "type cannot be null or empty");
+        requireNonNull(query, "query");
+
+        final HttpRequestBuilder builder = HttpRequest.builder();
+        if (params != null) {
+            params.forEach(builder::queryParam);
+        }
+
+        final byte[] content = version.codec().encode(ImmutableMap.of("query", query));
+
+        return builder.delete("/{index}/{type}/_delete_by_query")
+                      .pathParam("index", index)
+                      .pathParam("type", type)
+                      .content(MediaType.JSON, content)
+                      .build();
+    }
+}
diff --git a/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/v6/V6IndexFactory.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/v6/V6IndexFactory.java
new file mode 100644
index 0000000..e7f6b43
--- /dev/null
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/v6/V6IndexFactory.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.library.elasticsearch.requests.factory.v6;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableMap;
+import com.linecorp.armeria.common.HttpRequest;
+import com.linecorp.armeria.common.MediaType;
+import java.util.Map;
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+import org.apache.skywalking.library.elasticsearch.ElasticSearchVersion;
+import org.apache.skywalking.library.elasticsearch.requests.factory.IndexFactory;
+import org.apache.skywalking.library.elasticsearch.response.Mappings;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+@RequiredArgsConstructor
+final class V6IndexFactory implements IndexFactory {
+    private final ElasticSearchVersion version;
+
+    @Override
+    public HttpRequest exists(String index) {
+        checkArgument(!Strings.isNullOrEmpty(index), "index cannot be null or empty");
+
+        return HttpRequest.builder()
+                          .head("/{index}")
+                          .pathParam("index", index)
+                          .build();
+    }
+
+    @Override
+    public HttpRequest get(final String index) {
+        checkArgument(!Strings.isNullOrEmpty(index), "index cannot be null or empty");
+
+        return HttpRequest.builder()
+                          .get("/{index}")
+                          .pathParam("index", index)
+                          .build();
+    }
+
+    @SneakyThrows
+    @Override
+    public HttpRequest create(String index,
+                              Mappings mappings,
+                              Map<String, ?> settings) {
+        checkArgument(!Strings.isNullOrEmpty(index), "index cannot be null or empty");
+
+        final ImmutableMap.Builder<String, Object> bodyBuilder = ImmutableMap.builder();
+        if (mappings != null) {
+            bodyBuilder.put("mappings", mappings);
+        }
+        if (settings != null) {
+            bodyBuilder.put("settings", settings);
+        }
+        final ImmutableMap<String, Object> body = bodyBuilder.build();
+        final byte[] content = version.codec().encode(body);
+
+        return HttpRequest.builder()
+                          .put("/{index}")
+                          .pathParam("index", index)
+                          .content(MediaType.JSON, content)
+                          .build();
+    }
+
+    @Override
+    public HttpRequest delete(String index) {
+        checkArgument(!Strings.isNullOrEmpty(index), "index cannot be null or empty");
+
+        return HttpRequest.builder()
+                          .delete("/{index}")
+                          .pathParam("index", index)
+                          .build();
+    }
+
+    @SneakyThrows
+    @Override
+    public HttpRequest putMapping(String index, String type, Mappings mapping) {
+        checkArgument(!Strings.isNullOrEmpty(index), "index cannot be null or empty");
+        checkArgument(!Strings.isNullOrEmpty(type), "type cannot be null or empty");
+
+        final byte[] content = version.codec().encode(mapping);
+        return HttpRequest.builder()
+                          .put("/{index}/_mapping/{type}")
+                          .pathParam("index", index)
+                          .pathParam("type", type)
+                          .content(MediaType.JSON, content)
+                          .build();
+    }
+}
diff --git a/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/v6/V6RequestFactory.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/v6/V6RequestFactory.java
new file mode 100644
index 0000000..f81c52e
--- /dev/null
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/v6/V6RequestFactory.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.library.elasticsearch.requests.factory.v6;
+
+import lombok.Getter;
+import lombok.experimental.Accessors;
+import org.apache.skywalking.library.elasticsearch.ElasticSearchVersion;
+import org.apache.skywalking.library.elasticsearch.requests.factory.AliasFactory;
+import org.apache.skywalking.library.elasticsearch.requests.factory.BulkFactory;
+import org.apache.skywalking.library.elasticsearch.requests.factory.DocumentFactory;
+import org.apache.skywalking.library.elasticsearch.requests.factory.IndexFactory;
+import org.apache.skywalking.library.elasticsearch.requests.factory.RequestFactory;
+import org.apache.skywalking.library.elasticsearch.requests.factory.SearchFactory;
+import org.apache.skywalking.library.elasticsearch.requests.factory.TemplateFactory;
+import org.apache.skywalking.library.elasticsearch.requests.factory.common.CommonAliasFactory;
+import org.apache.skywalking.library.elasticsearch.requests.factory.common.CommonBulkFactory;
+import org.apache.skywalking.library.elasticsearch.requests.factory.common.CommonSearchFactory;
+
+@Getter
+@Accessors(fluent = true)
+public final class V6RequestFactory implements RequestFactory {
+    private final TemplateFactory template;
+    private final IndexFactory index;
+    private final AliasFactory alias;
+    private final DocumentFactory document;
+    private final SearchFactory search;
+    private final BulkFactory bulk;
+
+    public V6RequestFactory(final ElasticSearchVersion version) {
+        template = new V6TemplateFactory(version);
+        index = new V6IndexFactory(version);
+        alias = new CommonAliasFactory(version);
+        document = new V6DocumentFactory(version);
+        search = new CommonSearchFactory(version);
+        bulk = new CommonBulkFactory(version);
+    }
+}
diff --git a/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/v6/V6TemplateFactory.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/v6/V6TemplateFactory.java
new file mode 100644
index 0000000..671a83e
--- /dev/null
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/v6/V6TemplateFactory.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.library.elasticsearch.requests.factory.v6;
+
+import com.google.common.collect.ImmutableMap;
+import com.linecorp.armeria.common.HttpRequest;
+import com.linecorp.armeria.common.MediaType;
+import java.util.Collections;
+import java.util.Map;
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+import org.apache.skywalking.library.elasticsearch.ElasticSearchVersion;
+import org.apache.skywalking.library.elasticsearch.requests.factory.TemplateFactory;
+import org.apache.skywalking.library.elasticsearch.response.Mappings;
+
+/**
+ * This request factory is about legacy index templates, which are deprecated and will be replaced
+ * by the composable templates introduced in Elasticsearch 7.8. For information about composable
+ * templates, see Index templates, https://www.elastic.co/guide/en/elasticsearch/reference/current/index-templates.html
+ */
+@RequiredArgsConstructor
+final class V6TemplateFactory implements TemplateFactory {
+    private final ElasticSearchVersion version;
+
+    @Override
+    public HttpRequest exists(String name) {
+        return HttpRequest.builder()
+                          .get("/_template/{name}")
+                          .pathParam("name", name)
+                          .build();
+    }
+
+    @Override
+    public HttpRequest get(final String name) {
+        return HttpRequest.builder()
+                          .get("/_template/{name}")
+                          .pathParam("name", name)
+                          .build();
+    }
+
+    @Override
+    public HttpRequest delete(final String name) {
+        return HttpRequest.builder()
+                          .delete("/_template/{name}")
+                          .pathParam("name", name)
+                          .build();
+    }
+
+    @SneakyThrows
+    @Override
+    public HttpRequest createOrUpdate(String name, Map<String, ?> settings,
+                                      Mappings mappings, int order) {
+        final String[] patterns = new String[] {name + "-*"};
+        final Map<String, Object> aliases = ImmutableMap.of(name, Collections.emptyMap());
+        final Map<String, Object> template =
+            ImmutableMap.<String, Object>builder()
+                        .put("index_patterns", patterns)
+                        .put("aliases", aliases)
+                        .put("settings", settings)
+                        .put("mappings", mappings)
+                        .put("order", order)
+                        .build();
+
+        final byte[] content = version.codec().encode(template);
+
+        return HttpRequest.builder()
+                          .put("/_template/{name}")
+                          .pathParam("name", name)
+                          .content(MediaType.JSON, content)
+                          .build();
+    }
+}
diff --git a/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/v6/codec/V6Codec.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/v6/codec/V6Codec.java
new file mode 100644
index 0000000..3b16bc3
--- /dev/null
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/v6/codec/V6Codec.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.library.elasticsearch.requests.factory.v6.codec;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import java.io.InputStream;
+import org.apache.skywalking.library.elasticsearch.requests.IndexRequest;
+import org.apache.skywalking.library.elasticsearch.requests.UpdateRequest;
+import org.apache.skywalking.library.elasticsearch.requests.factory.Codec;
+import org.apache.skywalking.library.elasticsearch.response.Mappings;
+
+public final class V6Codec implements Codec {
+    public static final Codec INSTANCE = new V6Codec();
+
+    private static final ObjectMapper MAPPER = new ObjectMapper()
+        .setSerializationInclusion(JsonInclude.Include.NON_NULL)
+        // We added some serializers here and some in their item classes as annotation (e.g.
+        // org.apache.skywalking.library.elasticsearch.requests.search.Sorts),
+        // the basic idea is, if the item class is very basic and are the same serialization method
+        // in both 6.x and 7.x, we set the serializer in their item class as annotation to make it
+        // shared by 6.x and 7.x, without duplicating the serializer codes, otherwise, we add
+        // serializers for each version explicitly in the object mapper.
+        // The 2 methods to add serializers can be changed if some day the basic serializer cannot
+        // be shared between newer versions of ElasticSearch or vice versa.
+        .registerModule(
+            new SimpleModule()
+                .addSerializer(
+                    IndexRequest.class,
+                    new V6IndexRequestSerializer()
+                )
+                .addSerializer(
+                    UpdateRequest.class,
+                    new V6UpdateRequestSerializer()
+                )
+                .addSerializer(
+                    Mappings.class,
+                    new V6MappingsSerializer()
+                )
+                .addDeserializer(
+                    Mappings.class,
+                    new V6MappingsDeserializer()
+                )
+        )
+        .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+
+    @Override
+    public byte[] encode(final Object request) throws Exception {
+        return MAPPER.writeValueAsBytes(request);
+    }
+
+    @Override
+    public <T> T decode(final InputStream inputStream,
+                        final TypeReference<T> type) throws Exception {
+        return MAPPER.readValue(inputStream, type);
+    }
+
+    @Override
+    public <T> T decode(final InputStream inputStream,
+                        final Class<T> clazz) throws Exception {
+        return MAPPER.readValue(inputStream, clazz);
+    }
+}
diff --git a/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/v6/codec/V6IndexRequestSerializer.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/v6/codec/V6IndexRequestSerializer.java
new file mode 100644
index 0000000..467a444
--- /dev/null
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/v6/codec/V6IndexRequestSerializer.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.library.elasticsearch.requests.factory.v6.codec;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.io.SerializedString;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import java.io.IOException;
+import org.apache.skywalking.library.elasticsearch.requests.IndexRequest;
+
+final class V6IndexRequestSerializer extends JsonSerializer<IndexRequest> {
+    @Override
+    public void serialize(final IndexRequest value, final JsonGenerator gen,
+                          final SerializerProvider provider) throws IOException {
+        gen.setRootValueSeparator(new SerializedString("\n"));
+
+        gen.writeStartObject();
+        {
+            gen.writeFieldName("index");
+            gen.writeStartObject();
+            {
+                gen.writeStringField("_index", value.getIndex());
+                gen.writeStringField("_type", value.getType());
+                gen.writeStringField("_id", value.getId());
+            }
+            gen.writeEndObject();
+        }
+        gen.writeEndObject();
+
+        gen.writeObject(value.getDoc());
+    }
+}
diff --git a/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/v6/codec/V6MappingsDeserializer.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/v6/codec/V6MappingsDeserializer.java
new file mode 100644
index 0000000..aedcfb3
--- /dev/null
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/v6/codec/V6MappingsDeserializer.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.library.elasticsearch.requests.factory.v6.codec;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import java.io.IOException;
+import java.util.Map;
+import java.util.Optional;
+import org.apache.skywalking.library.elasticsearch.response.Mappings;
+
+final class V6MappingsDeserializer extends JsonDeserializer<Mappings> {
+    @Override
+    @SuppressWarnings("unchecked")
+    public Mappings deserialize(final JsonParser p, final DeserializationContext ctxt)
+        throws IOException {
+
+        final Map<String, Object> m =
+            p.readValueAs(new TypeReference<Map<String, Object>>() {
+            });
+        final Optional<Map.Entry<String, Object>> typeMapping =
+            m.entrySet()
+             .stream()
+             .filter(it -> it.getValue() instanceof Map)
+             .filter(it -> ((Map<String, Object>) it.getValue()).containsKey("properties"))
+             .peek(it -> it.setValue(((Map<?, ?>) it.getValue()).get("properties")))
+             .findFirst();
+
+        final Optional<Mappings> result = typeMapping.map(it -> {
+            final Mappings mappings = new Mappings();
+            mappings.setType(it.getKey());
+            mappings.setProperties((Map<String, Object>) it.getValue());
+            return mappings;
+        });
+        return result.orElse(null);
+    }
+}
diff --git a/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/v6/codec/V6MappingsSerializer.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/v6/codec/V6MappingsSerializer.java
new file mode 100644
index 0000000..dd20c3f
--- /dev/null
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/v6/codec/V6MappingsSerializer.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.library.elasticsearch.requests.factory.v6.codec;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import java.io.IOException;
+import org.apache.skywalking.library.elasticsearch.response.Mappings;
+
+final class V6MappingsSerializer extends JsonSerializer<Mappings> {
+
+    @Override
+    public void serialize(final Mappings value, final JsonGenerator gen,
+                          final SerializerProvider serializers)
+        throws IOException {
+        gen.writeStartObject();
+        {
+            gen.writeFieldName(value.getType());
+            gen.writeStartObject();
+            {
+                gen.writeObjectField("properties", value.getProperties());
+            }
+            gen.writeEndObject();
+        }
+        gen.writeEndObject();
+    }
+}
diff --git a/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/v6/codec/V6UpdateRequestSerializer.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/v6/codec/V6UpdateRequestSerializer.java
new file mode 100644
index 0000000..38fcb2b
--- /dev/null
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/v6/codec/V6UpdateRequestSerializer.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.library.elasticsearch.requests.factory.v6.codec;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.io.SerializedString;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import java.io.IOException;
+import org.apache.skywalking.library.elasticsearch.requests.UpdateRequest;
+
+final class V6UpdateRequestSerializer extends JsonSerializer<UpdateRequest> {
+    @Override
+    public void serialize(final UpdateRequest value, final JsonGenerator gen,
+                          final SerializerProvider provider) throws IOException {
+        gen.setRootValueSeparator(new SerializedString("\n"));
+
+        gen.writeStartObject();
+        {
+            gen.writeFieldName("update");
+            gen.writeStartObject();
+            {
+                gen.writeStringField("_index", value.getIndex());
+                gen.writeStringField("_type", value.getType());
+                gen.writeStringField("_id", value.getId());
+            }
+            gen.writeEndObject();
+        }
+
+        gen.writeEndObject();
+
+        gen.writeStartObject();
+        {
+            gen.writeFieldName("doc");
+            gen.writeObject(value.getDoc());
+        }
+        gen.writeEndObject();
+    }
+}
diff --git a/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/v7/V78RequestFactory.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/v7/V78RequestFactory.java
new file mode 100644
index 0000000..375dd28
--- /dev/null
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/v7/V78RequestFactory.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.library.elasticsearch.requests.factory.v7;
+
+import lombok.Getter;
+import lombok.experimental.Accessors;
+import org.apache.skywalking.library.elasticsearch.ElasticSearchVersion;
+import org.apache.skywalking.library.elasticsearch.requests.factory.AliasFactory;
+import org.apache.skywalking.library.elasticsearch.requests.factory.BulkFactory;
+import org.apache.skywalking.library.elasticsearch.requests.factory.DocumentFactory;
+import org.apache.skywalking.library.elasticsearch.requests.factory.IndexFactory;
+import org.apache.skywalking.library.elasticsearch.requests.factory.RequestFactory;
+import org.apache.skywalking.library.elasticsearch.requests.factory.SearchFactory;
+import org.apache.skywalking.library.elasticsearch.requests.factory.TemplateFactory;
+import org.apache.skywalking.library.elasticsearch.requests.factory.common.CommonAliasFactory;
+import org.apache.skywalking.library.elasticsearch.requests.factory.common.CommonBulkFactory;
+import org.apache.skywalking.library.elasticsearch.requests.factory.common.CommonSearchFactory;
+
+@Getter
+@Accessors(fluent = true)
+public final class V78RequestFactory implements RequestFactory {
+    private final TemplateFactory template;
+    private final IndexFactory index;
+    private final AliasFactory alias;
+    private final DocumentFactory document;
+    private final SearchFactory search;
+    private final BulkFactory bulk;
+
+    public V78RequestFactory(final ElasticSearchVersion version) {
+        template = new V78TemplateFactory(version);
+        index = new V7IndexFactory(version);
+        alias = new CommonAliasFactory(version);
+        document = new V7DocumentFactory(version);
+        search = new CommonSearchFactory(version);
+        bulk = new CommonBulkFactory(version);
+    }
+}
diff --git a/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/v7/V78TemplateFactory.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/v7/V78TemplateFactory.java
new file mode 100644
index 0000000..f57c725
--- /dev/null
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/v7/V78TemplateFactory.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.library.elasticsearch.requests.factory.v7;
+
+import com.google.common.collect.ImmutableMap;
+import com.linecorp.armeria.common.HttpRequest;
+import com.linecorp.armeria.common.MediaType;
+import java.util.Collections;
+import java.util.Map;
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+import org.apache.skywalking.library.elasticsearch.ElasticSearchVersion;
+import org.apache.skywalking.library.elasticsearch.requests.factory.TemplateFactory;
+import org.apache.skywalking.library.elasticsearch.response.Mappings;
+
+@RequiredArgsConstructor
+final class V78TemplateFactory implements TemplateFactory {
+    private final ElasticSearchVersion version;
+
+    @Override
+    public HttpRequest exists(String name) {
+        return HttpRequest.builder()
+                          .get("/_index_template/{name}")
+                          .pathParam("name", name)
+                          .build();
+    }
+
+    @Override
+    public HttpRequest get(final String name) {
+        return HttpRequest.builder()
+                          .get("/_index_template/{name}")
+                          .pathParam("name", name)
+                          .build();
+    }
+
+    @Override
+    public HttpRequest delete(final String name) {
+        return HttpRequest.builder()
+                          .delete("/_index_template/{name}")
+                          .pathParam("name", name)
+                          .build();
+    }
+
+    @SneakyThrows
+    @Override
+    public HttpRequest createOrUpdate(String name, Map<String, ?> settings,
+                                      Mappings mappings, int order) {
+        final String[] patterns = new String[] {name + "-*"};
+        final Map<String, Object> aliases = ImmutableMap.of(name, Collections.emptyMap());
+        final Map<String, Object> template =
+            ImmutableMap.<String, Object>builder()
+                        .put("index_patterns", patterns)
+                        .put(
+                            "template",
+                            ImmutableMap.builder()
+                                        .put("aliases", aliases)
+                                        .put("settings", settings)
+                                        .put("mappings", mappings)
+                                        .build()
+                        )
+                        .build();
+
+        final byte[] content = version.codec().encode(template);
+
+        return HttpRequest.builder()
+                          .put("/_index_template/{name}")
+                          .pathParam("name", name)
+                          .content(MediaType.JSON, content)
+                          .build();
+    }
+}
diff --git a/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/v7/V7DocumentFactory.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/v7/V7DocumentFactory.java
new file mode 100644
index 0000000..49d058d
--- /dev/null
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/v7/V7DocumentFactory.java
@@ -0,0 +1,168 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.library.elasticsearch.requests.factory.v7;
+
+import com.google.common.collect.ImmutableMap;
+import com.linecorp.armeria.common.HttpRequest;
+import com.linecorp.armeria.common.HttpRequestBuilder;
+import com.linecorp.armeria.common.MediaType;
+import java.util.Map;
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.skywalking.library.elasticsearch.ElasticSearchVersion;
+import org.apache.skywalking.library.elasticsearch.requests.IndexRequest;
+import org.apache.skywalking.library.elasticsearch.requests.UpdateRequest;
+import org.apache.skywalking.library.elasticsearch.requests.factory.DocumentFactory;
+import org.apache.skywalking.library.elasticsearch.requests.search.Query;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Strings.isNullOrEmpty;
+import static com.google.common.collect.Iterables.isEmpty;
+import static java.util.Objects.requireNonNull;
+
+@Slf4j
+@RequiredArgsConstructor
+final class V7DocumentFactory implements DocumentFactory {
+    private final ElasticSearchVersion version;
+
+    @Override
+    public HttpRequest exist(String index, String type, String id) {
+        checkArgument(!isNullOrEmpty(index), "index cannot be null or empty");
+        checkArgument(!isNullOrEmpty(type), "type cannot be null or empty");
+        checkArgument(!isNullOrEmpty(id), "id cannot be null or empty");
+
+        return HttpRequest.builder()
+                          .head("/{index}/_doc/{id}")
+                          .pathParam("index", index)
+                          .pathParam("id", id)
+                          .build();
+    }
+
+    @Override
+    public HttpRequest get(String index, String type, String id) {
+        checkArgument(!isNullOrEmpty(index), "index cannot be null or empty");
+        checkArgument(!isNullOrEmpty(type), "type cannot be null or empty");
+        checkArgument(!isNullOrEmpty(id), "id cannot be null or empty");
+
+        return HttpRequest.builder()
+                          .get("/{index}/_doc/{id}")
+                          .pathParam("index", index)
+                          .pathParam("id", id)
+                          .build();
+    }
+
+    @SneakyThrows
+    @Override
+    public HttpRequest mget(String index, String type, Iterable<String> ids) {
+        checkArgument(!isNullOrEmpty(index), "index cannot be null or empty");
+        checkArgument(!isNullOrEmpty(type), "type cannot be null or empty");
+        checkArgument(ids != null && !isEmpty(ids), "ids cannot be null or empty");
+
+        final Map<String, Iterable<String>> m = ImmutableMap.of("ids", ids);
+        final byte[] content = version.codec().encode(m);
+
+        if (log.isDebugEnabled()) {
+            log.debug("mget {} ids: {}", index, ids);
+        }
+
+        return HttpRequest.builder()
+                          .get("/{index}/_doc/_mget")
+                          .pathParam("index", index)
+                          .content(MediaType.JSON, content)
+                          .build();
+    }
+
+    @SneakyThrows
+    @Override
+    public HttpRequest index(IndexRequest request, Map<String, ?> params) {
+        requireNonNull(request, "request");
+
+        final String index = request.getIndex();
+        final String type = request.getType();
+        final String id = request.getId();
+        final Map<String, ?> doc = request.getDoc();
+
+        checkArgument(!isNullOrEmpty(index), "request.index cannot be null or empty");
+        checkArgument(!isNullOrEmpty(type), "request.type cannot be null or empty");
+        checkArgument(!isNullOrEmpty(id), "request.id cannot be null or empty");
+
+        final HttpRequestBuilder builder = HttpRequest.builder();
+        if (params != null) {
+            params.forEach(builder::queryParam);
+        }
+        final byte[] content = version.codec().encode(doc);
+
+        builder.put("/{index}/_doc/{id}")
+               .pathParam("index", index)
+               .pathParam("id", id)
+               .content(MediaType.JSON, content);
+
+        return builder.build();
+    }
+
+    @SneakyThrows
+    @Override
+    public HttpRequest update(UpdateRequest request, Map<String, ?> params) {
+        requireNonNull(request, "request");
+
+        final String index = request.getIndex();
+        final String type = request.getType();
+        final String id = request.getId();
+        final Map<String, Object> doc = request.getDoc();
+
+        checkArgument(!isNullOrEmpty(index), "index cannot be null or empty");
+        checkArgument(!isNullOrEmpty(type), "type cannot be null or empty");
+        checkArgument(!isNullOrEmpty(id), "id cannot be null or empty");
+        checkArgument(doc != null && !isEmpty(doc.entrySet()), "doc cannot be null or empty");
+
+        final HttpRequestBuilder builder = HttpRequest.builder();
+        if (params != null) {
+            params.forEach(builder::queryParam);
+        }
+        final byte[] content = version.codec().encode(ImmutableMap.of("doc", doc));
+
+        builder.post("/{index}/_doc/{id}/_update")
+               .pathParam("index", index)
+               .pathParam("id", id)
+               .content(MediaType.JSON, content);
+
+        return builder.build();
+    }
+
+    @SneakyThrows
+    @Override
+    public HttpRequest delete(String index, String type, Query query,
+                              Map<String, ?> params) {
+        checkArgument(!isNullOrEmpty(index), "index cannot be null or empty");
+        checkArgument(!isNullOrEmpty(type), "type cannot be null or empty");
+        requireNonNull(query, "query");
+
+        final HttpRequestBuilder builder = HttpRequest.builder();
+        if (params != null) {
+            params.forEach(builder::queryParam);
+        }
+
+        final byte[] content = version.codec().encode(ImmutableMap.of("query", query));
+
+        return builder.delete("/{index}/_doc/_delete_by_query")
+                      .pathParam("index", index)
+                      .content(MediaType.JSON, content)
+                      .build();
+    }
+}
diff --git a/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/v7/V7IndexFactory.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/v7/V7IndexFactory.java
new file mode 100644
index 0000000..49d2248
--- /dev/null
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/v7/V7IndexFactory.java
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.library.elasticsearch.requests.factory.v7;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableMap;
+import com.linecorp.armeria.common.HttpRequest;
+import com.linecorp.armeria.common.MediaType;
+import java.util.Map;
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+import org.apache.skywalking.library.elasticsearch.ElasticSearchVersion;
+import org.apache.skywalking.library.elasticsearch.requests.factory.IndexFactory;
+import org.apache.skywalking.library.elasticsearch.response.Mappings;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+@RequiredArgsConstructor
+final class V7IndexFactory implements IndexFactory {
+    private final ElasticSearchVersion version;
+
+    @Override
+    public HttpRequest exists(String index) {
+        checkArgument(!Strings.isNullOrEmpty(index), "index cannot be null or empty");
+
+        return HttpRequest.builder()
+                          .head("/{index}")
+                          .pathParam("index", index)
+                          .build();
+    }
+
+    @Override
+    public HttpRequest get(final String index) {
+        checkArgument(!Strings.isNullOrEmpty(index), "index cannot be null or empty");
+
+        return HttpRequest.builder()
+                          .get("/{index}")
+                          .pathParam("index", index)
+                          .build();
+    }
+
+    @SneakyThrows
+    @Override
+    public HttpRequest create(String index,
+                              Mappings mappings,
+                              Map<String, ?> settings) {
+        checkArgument(!Strings.isNullOrEmpty(index), "index cannot be null or empty");
+
+        final ImmutableMap.Builder<String, Object> bodyBuilder = ImmutableMap.builder();
+        if (mappings != null) {
+            bodyBuilder.put("mappings", mappings);
+        }
+        if (settings != null) {
+            bodyBuilder.put("settings", settings);
+        }
+        final ImmutableMap<String, Object> body = bodyBuilder.build();
+        final byte[] content = version.codec().encode(body);
+
+        return HttpRequest.builder()
+                          .put("/{index}")
+                          .pathParam("index", index)
+                          .content(MediaType.JSON, content)
+                          .build();
+    }
+
+    @Override
+    public HttpRequest delete(String index) {
+        checkArgument(!Strings.isNullOrEmpty(index), "index cannot be null or empty");
+
+        return HttpRequest.builder()
+                          .delete("/{index}")
+                          .pathParam("index", index)
+                          .build();
+    }
+
+    @SneakyThrows
+    @Override
+    public HttpRequest putMapping(String index, String type, Mappings mapping) {
+        checkArgument(!Strings.isNullOrEmpty(index), "index cannot be null or empty");
+
+        final byte[] content = version.codec().encode(mapping);
+        return HttpRequest.builder()
+                          .put("/{index}/_mapping")
+                          .pathParam("index", index)
+                          .content(MediaType.JSON, content)
+                          .build();
+    }
+}
diff --git a/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/v7/V7RequestFactory.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/v7/V7RequestFactory.java
new file mode 100644
index 0000000..9fae8d4
--- /dev/null
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/v7/V7RequestFactory.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.library.elasticsearch.requests.factory.v7;
+
+import lombok.Getter;
+import lombok.experimental.Accessors;
+import org.apache.skywalking.library.elasticsearch.ElasticSearchVersion;
+import org.apache.skywalking.library.elasticsearch.requests.factory.AliasFactory;
+import org.apache.skywalking.library.elasticsearch.requests.factory.BulkFactory;
+import org.apache.skywalking.library.elasticsearch.requests.factory.DocumentFactory;
+import org.apache.skywalking.library.elasticsearch.requests.factory.IndexFactory;
+import org.apache.skywalking.library.elasticsearch.requests.factory.RequestFactory;
+import org.apache.skywalking.library.elasticsearch.requests.factory.SearchFactory;
+import org.apache.skywalking.library.elasticsearch.requests.factory.TemplateFactory;
+import org.apache.skywalking.library.elasticsearch.requests.factory.common.CommonAliasFactory;
+import org.apache.skywalking.library.elasticsearch.requests.factory.common.CommonBulkFactory;
+import org.apache.skywalking.library.elasticsearch.requests.factory.common.CommonSearchFactory;
+
+@Getter
+@Accessors(fluent = true)
+public final class V7RequestFactory implements RequestFactory {
+    private final TemplateFactory template;
+    private final IndexFactory index;
+    private final AliasFactory alias;
+    private final DocumentFactory document;
+    private final SearchFactory search;
+    private final BulkFactory bulk;
+
+    public V7RequestFactory(final ElasticSearchVersion version) {
+        template = new V7TemplateFactory(version);
+        index = new V7IndexFactory(version);
+        alias = new CommonAliasFactory(version);
+        document = new V7DocumentFactory(version);
+        search = new CommonSearchFactory(version);
+        bulk = new CommonBulkFactory(version);
+    }
+}
diff --git a/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/v7/V7TemplateFactory.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/v7/V7TemplateFactory.java
new file mode 100644
index 0000000..776be59
--- /dev/null
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/v7/V7TemplateFactory.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.library.elasticsearch.requests.factory.v7;
+
+import com.google.common.collect.ImmutableMap;
+import com.linecorp.armeria.common.HttpRequest;
+import com.linecorp.armeria.common.MediaType;
+import java.util.Collections;
+import java.util.Map;
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+import org.apache.skywalking.library.elasticsearch.ElasticSearchVersion;
+import org.apache.skywalking.library.elasticsearch.requests.factory.TemplateFactory;
+import org.apache.skywalking.library.elasticsearch.response.Mappings;
+
+@RequiredArgsConstructor
+final class V7TemplateFactory implements TemplateFactory {
+    private final ElasticSearchVersion version;
+
+    @Override
+    public HttpRequest exists(String name) {
+        return HttpRequest.builder()
+                          .get("/_template/{name}")
+                          .pathParam("name", name)
+                          .build();
+    }
+
+    @Override
+    public HttpRequest get(final String name) {
+        return HttpRequest.builder()
+                          .get("/_template/{name}")
+                          .pathParam("name", name)
+                          .build();
+    }
+
+    @Override
+    public HttpRequest delete(final String name) {
+        return HttpRequest.builder()
+                          .delete("/_template/{name}")
+                          .pathParam("name", name)
+                          .build();
+    }
+
+    @SneakyThrows
+    @Override
+    public HttpRequest createOrUpdate(String name, Map<String, ?> settings,
+                                      Mappings mappings, int order) {
+        final String[] patterns = new String[] {name + "-*"};
+        final Map<String, Object> aliases = ImmutableMap.of(name, Collections.emptyMap());
+        final Map<String, Object> template =
+            ImmutableMap.<String, Object>builder()
+                        .put("index_patterns", patterns)
+                        .put("aliases", aliases)
+                        .put("settings", settings)
+                        .put("mappings", mappings)
+                        .build();
+
+        final byte[] content = version.codec().encode(template);
+
+        return HttpRequest.builder()
+                          .put("/_template/{name}")
+                          .pathParam("name", name)
+                          .content(MediaType.JSON, content)
+                          .build();
+    }
+}
diff --git a/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/v7/codec/V7Codec.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/v7/codec/V7Codec.java
new file mode 100644
index 0000000..709998a
--- /dev/null
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/v7/codec/V7Codec.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.library.elasticsearch.requests.factory.v7.codec;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import java.io.InputStream;
+import org.apache.skywalking.library.elasticsearch.requests.IndexRequest;
+import org.apache.skywalking.library.elasticsearch.requests.UpdateRequest;
+import org.apache.skywalking.library.elasticsearch.requests.factory.Codec;
+import org.apache.skywalking.library.elasticsearch.response.Mappings;
+
+public final class V7Codec implements Codec {
+    public static final Codec INSTANCE = new V7Codec();
+
+    private static final ObjectMapper MAPPER = new ObjectMapper()
+        .setSerializationInclusion(JsonInclude.Include.NON_NULL)
+        // We added some serializers here and some in their item classes as annotation (e.g.
+        // org.apache.skywalking.library.elasticsearch.requests.search.Sorts),
+        // the basic idea is, if the item class is very basic and are the same serialization method
+        // in both 6.x and 7.x, we set the serializer in their item class as annotation to make it
+        // shared by 6.x and 7.x, without duplicating the serializer codes, otherwise, we add
+        // serializers for each version explicitly in the object mapper.
+        // The 2 methods to add serializers can be changed if some day the basic serializer cannot
+        // be shared between newer versions of ElasticSearch or vice versa.
+        .registerModule(
+            new SimpleModule()
+                .addSerializer(
+                    IndexRequest.class,
+                    new V7IndexRequestSerializer()
+                )
+                .addSerializer(
+                    UpdateRequest.class,
+                    new V7UpdateRequestSerializer()
+                )
+                .addDeserializer(
+                    Mappings.class,
+                    new V7MappingsDeserializer()
+                )
+        )
+        .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+
+    @Override
+    public byte[] encode(final Object request) throws Exception {
+        return MAPPER.writeValueAsBytes(request);
+    }
+
+    @Override
+    public <T> T decode(final InputStream inputStream,
+                        final TypeReference<T> type) throws Exception {
+        return MAPPER.readValue(inputStream, type);
+    }
+
+    @Override
+    public <T> T decode(final InputStream inputStream,
+                        final Class<T> clazz) throws Exception {
+        return MAPPER.readValue(inputStream, clazz);
+    }
+}
diff --git a/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/v7/codec/V7IndexRequestSerializer.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/v7/codec/V7IndexRequestSerializer.java
new file mode 100644
index 0000000..8eacf72
--- /dev/null
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/v7/codec/V7IndexRequestSerializer.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.library.elasticsearch.requests.factory.v7.codec;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.io.SerializedString;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import java.io.IOException;
+import org.apache.skywalking.library.elasticsearch.requests.IndexRequest;
+
+final class V7IndexRequestSerializer extends JsonSerializer<IndexRequest> {
+    @Override
+    public void serialize(final IndexRequest value, final JsonGenerator gen,
+                          final SerializerProvider provider) throws IOException {
+        gen.setRootValueSeparator(new SerializedString("\n"));
+
+        gen.writeStartObject();
+        {
+            gen.writeFieldName("index");
+            gen.writeStartObject();
+            {
+                gen.writeStringField("_index", value.getIndex());
+                gen.writeStringField("_id", value.getId());
+            }
+            gen.writeEndObject();
+        }
+        gen.writeEndObject();
+
+        gen.writeObject(value.getDoc());
+    }
+}
diff --git a/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/v7/codec/V7MappingsDeserializer.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/v7/codec/V7MappingsDeserializer.java
new file mode 100644
index 0000000..830f64a
--- /dev/null
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/v7/codec/V7MappingsDeserializer.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.library.elasticsearch.requests.factory.v7.codec;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.Map;
+import org.apache.skywalking.library.elasticsearch.response.Mappings;
+
+final class V7MappingsDeserializer extends JsonDeserializer<Mappings> {
+    @Override
+    @SuppressWarnings("unchecked")
+    public Mappings deserialize(final JsonParser p, final DeserializationContext ctxt)
+        throws IOException {
+
+        final Map<String, Object> m =
+            p.readValueAs(new TypeReference<Map<String, Object>>() {
+            });
+
+        final Iterator<Map.Entry<String, Object>> it = m.entrySet().iterator();
+        if (it.hasNext()) {
+            final Map.Entry<String, Object> first = it.next();
+            final Mappings mappings = new Mappings();
+            mappings.setType(first.getKey());
+            mappings.setProperties((Map<String, Object>) first.getValue());
+            return mappings;
+        }
+        return null;
+    }
+}
diff --git a/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/v7/codec/V7UpdateRequestSerializer.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/v7/codec/V7UpdateRequestSerializer.java
new file mode 100644
index 0000000..017b21b
--- /dev/null
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/factory/v7/codec/V7UpdateRequestSerializer.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.library.elasticsearch.requests.factory.v7.codec;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.io.SerializedString;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import java.io.IOException;
+import org.apache.skywalking.library.elasticsearch.requests.UpdateRequest;
+
+final class V7UpdateRequestSerializer extends JsonSerializer<UpdateRequest> {
+    @Override
+    public void serialize(final UpdateRequest value, final JsonGenerator gen,
+                          final SerializerProvider provider) throws IOException {
+        gen.setRootValueSeparator(new SerializedString("\n"));
+
+        gen.writeStartObject();
+        {
+            gen.writeFieldName("update");
+            gen.writeStartObject();
+            {
+                gen.writeStringField("_index", value.getIndex());
+                gen.writeStringField("_id", value.getId());
+            }
+            gen.writeEndObject();
+        }
+
+        gen.writeEndObject();
+
+        gen.writeStartObject();
+        {
+            gen.writeFieldName("doc");
+            gen.writeObject(value.getDoc());
+        }
+        gen.writeEndObject();
+    }
+}
diff --git a/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/BoolQuery.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/BoolQuery.java
new file mode 100644
index 0000000..3eccaf9
--- /dev/null
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/BoolQuery.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.library.elasticsearch.requests.search;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.util.List;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@Getter
+@JsonSerialize(using = BoolQuery.BoolQuerySerializer.class)
+@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
+public final class BoolQuery extends Query {
+    private final ImmutableList<Query> must;
+    private final ImmutableList<Query> mustNot;
+    private final ImmutableList<Query> should;
+    private final ImmutableList<Query> shouldNot;
+
+    static final class BoolQuerySerializer extends JsonSerializer<BoolQuery> {
+        static final String NAME = "bool";
+        static final String MUST = "must";
+        static final String MUST_NOT = "must_not";
+        static final String SHOULD = "should";
+        static final String SHOULD_NOT = "should_not";
+
+        @Override
+        public void serialize(final BoolQuery value, final JsonGenerator gen,
+                              final SerializerProvider provider) throws IOException {
+            gen.writeStartObject();
+            {
+                gen.writeFieldName(NAME);
+                gen.writeStartObject();
+                {
+                    writeArray(gen, MUST, value.getMust());
+                    writeArray(gen, MUST_NOT, value.getMustNot());
+                    writeArray(gen, SHOULD, value.getShould());
+                    writeArray(gen, SHOULD_NOT, value.getShouldNot());
+                }
+                gen.writeEndObject();
+            }
+            gen.writeEndObject();
+        }
+
+        private void writeArray(final JsonGenerator gen, final String name,
+                                final List<Query> array) throws IOException {
+            if (array == null || array.isEmpty()) {
+                return;
+            }
+
+            gen.writeFieldName(name);
+            gen.writeStartArray();
+            {
+                for (final Query query : array) {
+                    gen.writeObject(query);
+                }
+            }
+            gen.writeEndArray();
+        }
+    }
+}
diff --git a/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/BoolQueryBuilder.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/BoolQueryBuilder.java
new file mode 100644
index 0000000..aacafaa
--- /dev/null
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/BoolQueryBuilder.java
@@ -0,0 +1,133 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.library.elasticsearch.requests.search;
+
+import com.google.common.collect.ImmutableList;
+
+import static java.util.Objects.requireNonNull;
+
+public final class BoolQueryBuilder implements QueryBuilder {
+    private ImmutableList.Builder<Query> must;
+    private ImmutableList.Builder<Query> mustNot;
+    private ImmutableList.Builder<Query> should;
+    private ImmutableList.Builder<Query> shouldNot;
+
+    BoolQueryBuilder() {
+    }
+
+    public BoolQueryBuilder must(Query query) {
+        requireNonNull(query, "query");
+        must().add(query);
+        return this;
+    }
+
+    public BoolQueryBuilder must(QueryBuilder queryBuilder) {
+        requireNonNull(queryBuilder, "queryBuilder");
+        return must(queryBuilder.build());
+    }
+
+    public BoolQueryBuilder mustNot(Query query) {
+        requireNonNull(query, "query");
+        mustNot().add(query);
+        return this;
+    }
+
+    public BoolQueryBuilder mustNot(QueryBuilder queryBuilder) {
+        requireNonNull(queryBuilder, "queryBuilder");
+        return mustNot(queryBuilder.build());
+    }
+
+    public BoolQueryBuilder should(Query query) {
+        requireNonNull(query, "query");
+        should().add(query);
+        return this;
+    }
+
+    public BoolQueryBuilder should(QueryBuilder queryBuilder) {
+        requireNonNull(queryBuilder, "queryBuilder");
+        return should(queryBuilder.build());
+    }
+
+    public BoolQueryBuilder shouldNot(Query query) {
+        requireNonNull(query, "query");
+        shouldNot().add(query);
+        return this;
+    }
+
+    public BoolQueryBuilder shouldNot(QueryBuilder queryBuilder) {
+        requireNonNull(queryBuilder, "queryBuilder");
+        return shouldNot(queryBuilder.build());
+    }
+
+    private ImmutableList.Builder<Query> must() {
+        if (must == null) {
+            must = ImmutableList.builder();
+        }
+        return must;
+    }
+
+    private ImmutableList.Builder<Query> mustNot() {
+        if (mustNot == null) {
+            mustNot = ImmutableList.builder();
+        }
+        return mustNot;
+    }
+
+    private ImmutableList.Builder<Query> should() {
+        if (should == null) {
+            should = ImmutableList.builder();
+        }
+        return should;
+    }
+
+    private ImmutableList.Builder<Query> shouldNot() {
+        if (shouldNot == null) {
+            shouldNot = ImmutableList.builder();
+        }
+        return shouldNot;
+    }
+
+    @Override
+    public Query build() {
+        final ImmutableList<Query> must;
+        if (this.must == null) {
+            must = null;
+        } else {
+            must = this.must.build();
+        }
+        final ImmutableList<Query> should;
+        if (this.should == null) {
+            should = null;
+        } else {
+            should = this.should.build();
+        }
+        final ImmutableList<Query> mustNot;
+        if (this.mustNot == null) {
+            mustNot = null;
+        } else {
+            mustNot = this.mustNot.build();
+        }
+        final ImmutableList<Query> shouldNot;
+        if (this.shouldNot == null) {
+            shouldNot = null;
+        } else {
+            shouldNot = this.shouldNot.build();
+        }
+        return new BoolQuery(must, mustNot, should, shouldNot);
+    }
+}
diff --git a/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/IdsQuery.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/IdsQuery.java
new file mode 100644
index 0000000..affe970
--- /dev/null
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/IdsQuery.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.library.elasticsearch.requests.search;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@Getter
+@JsonSerialize(using = IdsQuery.Serializer.class)
+@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
+public final class IdsQuery extends Query {
+    private static final String NAME = "ids";
+
+    private final ImmutableList<String> ids;
+
+    static class Serializer extends JsonSerializer<IdsQuery> {
+        @Override
+        public void serialize(final IdsQuery value, final JsonGenerator gen,
+                              final SerializerProvider provider)
+            throws IOException {
+            gen.writeStartObject();
+            {
+                gen.writeFieldName(NAME);
+                gen.writeStartObject();
+                {
+                    gen.writeObjectField("values", value.getIds());
+                }
+                gen.writeEndObject();
+            }
+            gen.writeEndObject();
+        }
+    }
+}
diff --git a/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/MatchPhaseQuery.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/MatchPhaseQuery.java
new file mode 100644
index 0000000..faafc11
--- /dev/null
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/MatchPhaseQuery.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.library.elasticsearch.requests.search;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import java.io.IOException;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@Getter
+@JsonSerialize(using = MatchPhaseQuery.Serializer.class)
+@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
+public final class MatchPhaseQuery extends Query {
+    private final String name;
+    private final String text;
+
+    static class Serializer extends JsonSerializer<MatchPhaseQuery> {
+        @Override
+        public void serialize(final MatchPhaseQuery value, final JsonGenerator gen,
+                              final SerializerProvider provider)
+            throws IOException {
+            gen.writeStartObject();
+            {
+                gen.writeFieldName("match_phrase");
+                gen.writeStartObject();
+                {
+                    gen.writeStringField(value.getName(), value.getText());
+                }
+                gen.writeEndObject();
+            }
+            gen.writeEndObject();
+        }
+    }
+}
diff --git a/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/MatchQuery.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/MatchQuery.java
new file mode 100644
index 0000000..fa44e99
--- /dev/null
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/MatchQuery.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.library.elasticsearch.requests.search;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import java.io.IOException;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@Getter
+@JsonSerialize(using = MatchQuery.Serializer.class)
+@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
+public final class MatchQuery extends Query {
+    private final String name;
+    private final String text;
+
+    static class Serializer extends JsonSerializer<MatchQuery> {
+        @Override
+        public void serialize(final MatchQuery value, final JsonGenerator gen,
+                              final SerializerProvider provider)
+            throws IOException {
+            gen.writeFieldName("match");
+            gen.writeStartObject();
+            {
+                gen.writeStringField(value.getName(), value.getText());
+            }
+            gen.writeEndObject();
+        }
+    }
+}
diff --git a/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/Query.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/Query.java
new file mode 100644
index 0000000..ed914e1
--- /dev/null
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/Query.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.library.elasticsearch.requests.search;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+import java.util.Arrays;
+import java.util.List;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Represents criteria when matching documents in ElasticSearch.
+ */
+public abstract class Query {
+    public static RangeQueryBuilder range(String name) {
+        checkArgument(!Strings.isNullOrEmpty(name), "name cannot be blank");
+        return new RangeQueryBuilder(name);
+    }
+
+    public static TermQuery term(String name, Object value) {
+        checkArgument(!Strings.isNullOrEmpty(name), "name cannot be blank");
+        requireNonNull(value, "value");
+        return new TermQuery(name, value);
+    }
+
+    public static TermsQuery terms(String name, Object... values) {
+        checkArgument(!Strings.isNullOrEmpty(name), "name cannot be blank");
+        requireNonNull(values, "values");
+        return new TermsQuery(name, Arrays.asList(values));
+    }
+
+    public static TermsQuery terms(String name, List<?> values) {
+        checkArgument(!Strings.isNullOrEmpty(name), "name cannot be blank");
+        requireNonNull(values, "values");
+        return new TermsQuery(name, values);
+    }
+
+    public static MatchQuery match(String name, String text) {
+        checkArgument(!Strings.isNullOrEmpty(name), "name cannot be blank");
+        checkArgument(!Strings.isNullOrEmpty(text), "text cannot be blank");
+        return new MatchQuery(name, text);
+    }
+
+    public static MatchQuery match(String name, Object value) {
+        requireNonNull(value, "value");
+        return match(name, value.toString());
+    }
+
+    public static MatchPhaseQuery matchPhrase(String name, String text) {
+        checkArgument(!Strings.isNullOrEmpty(name), "name cannot be blank");
+        checkArgument(!Strings.isNullOrEmpty(text), "text cannot be blank");
+        return new MatchPhaseQuery(name, text);
+    }
+
+    public static IdsQuery ids(String... ids) {
+        requireNonNull(ids, "ids");
+        checkArgument(ids.length > 0, "ids cannot be empty");
+        return ids(Arrays.asList(ids));
+    }
+
+    public static IdsQuery ids(Iterable<String> ids) {
+        requireNonNull(ids, "ids");
+        return new IdsQuery(ImmutableList.<String>builder().addAll(ids).build());
+    }
+
+    public static BoolQueryBuilder bool() {
+        return new BoolQueryBuilder();
+    }
+}
diff --git a/oap-server/server-library/library-client/src/main/java/org/apache/skywalking/oap/server/library/client/elasticsearch/IndexNameMaker.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/QueryBuilder.java
similarity index 79%
rename from oap-server/server-library/library-client/src/main/java/org/apache/skywalking/oap/server/library/client/elasticsearch/IndexNameMaker.java
rename to oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/QueryBuilder.java
index 237e3dd..8b7a948 100644
--- a/oap-server/server-library/library-client/src/main/java/org/apache/skywalking/oap/server/library/client/elasticsearch/IndexNameMaker.java
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/QueryBuilder.java
@@ -13,15 +13,10 @@
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
- *
  */
 
-package org.apache.skywalking.oap.server.library.client.elasticsearch;
+package org.apache.skywalking.library.elasticsearch.requests.search;
 
-/**
- * Implementation supports to get concrete index name
- */
-@FunctionalInterface
-public interface IndexNameMaker {
-    String[] make();
+public interface QueryBuilder {
+    Query build();
 }
diff --git a/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/RangeQuery.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/RangeQuery.java
new file mode 100644
index 0000000..faef7cc
--- /dev/null
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/RangeQuery.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.library.elasticsearch.requests.search;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import java.io.IOException;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@Getter
+@JsonSerialize(using = RangeQuery.Serializer.class)
+@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
+public final class RangeQuery extends Query {
+    private final String name;
+    private final Object gte;
+    private final Object gt;
+    private final Object lte;
+    private final Object lt;
+    private final Double boost;
+
+    static final class Serializer extends JsonSerializer<RangeQuery> {
+        static final String NAME = "range";
+
+        @Override
+        public void serialize(final RangeQuery value, final JsonGenerator gen,
+                              final SerializerProvider provider) throws IOException {
+            gen.writeStartObject();
+            {
+                gen.writeFieldName(NAME);
+                gen.writeStartObject();
+                {
+                    gen.writeFieldName(value.getName());
+                    gen.writeStartObject();
+
+                    if (value.getGte() != null) {
+                        provider.defaultSerializeField("gte", value.getGte(), gen);
+                    }
+                    if (value.getLte() != null) {
+                        provider.defaultSerializeField("lte", value.getLte(), gen);
+                    }
+                    if (value.getGt() != null) {
+                        provider.defaultSerializeField("gt", value.getGt(), gen);
+                    }
+                    if (value.getLt() != null) {
+                        provider.defaultSerializeField("lt", value.getLt(), gen);
+                    }
+                    if (value.getBoost() != null) {
+                        provider.defaultSerializeField("boost", value.getBoost(), gen);
+                    }
+
+                    gen.writeEndObject();
+                }
+                gen.writeEndObject();
+            }
+            gen.writeEndObject();
+        }
+    }
+}
diff --git a/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/RangeQueryBuilder.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/RangeQueryBuilder.java
new file mode 100644
index 0000000..1430862
--- /dev/null
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/RangeQueryBuilder.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.library.elasticsearch.requests.search;
+
+import com.google.common.base.Strings;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.Objects.requireNonNull;
+
+public final class RangeQueryBuilder implements QueryBuilder {
+    private final String name;
+    private Object gte;
+    private Object gt;
+    private Object lte;
+    private Object lt;
+    private Double boost;
+
+    RangeQueryBuilder(String name) {
+        checkArgument(!Strings.isNullOrEmpty(name), "name cannot be null or empty");
+
+        this.name = name;
+    }
+
+    public RangeQueryBuilder gte(Object gte) {
+        this.gte = requireNonNull(gte, "gte");
+        return this;
+    }
+
+    public RangeQueryBuilder gt(Object gt) {
+        this.gt = requireNonNull(gt, "gt");
+        return this;
+    }
+
+    public RangeQueryBuilder lte(Object lte) {
+        this.lte = requireNonNull(lte, "lte");
+        return this;
+    }
+
+    public RangeQueryBuilder lt(Object lt) {
+        this.lt = requireNonNull(lt, "lt");
+        return this;
+    }
+
+    public RangeQueryBuilder boost(Double boost) {
+        requireNonNull(boost, "boost");
+
+        this.boost = boost;
+        return this;
+    }
+
+    @Override
+    public Query build() {
+        return new RangeQuery(name, gte, gt, lte, lt, boost);
+    }
+}
diff --git a/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/base/TimeRangeIndexNameMaker.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/Search.java
similarity index 50%
copy from oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/base/TimeRangeIndexNameMaker.java
copy to oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/Search.java
index 1b73b54..7b0aec8 100644
--- a/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/base/TimeRangeIndexNameMaker.java
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/Search.java
@@ -13,30 +13,31 @@
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
- *
  */
 
-package org.apache.skywalking.oap.server.storage.plugin.elasticsearch.base;
+package org.apache.skywalking.library.elasticsearch.requests.search;
 
-import org.apache.skywalking.oap.server.library.client.elasticsearch.IndexNameMaker;
+import com.google.common.collect.ImmutableMap;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import org.apache.skywalking.library.elasticsearch.requests.search.aggregation.Aggregation;
 
 /**
- * the time range index maker works for super size dataset
+ * Represents the criteria when searching documents in ElasticSearch.
+ *
+ * @see SearchBuilder to build an immutable instance of this class.
  */
-public class TimeRangeIndexNameMaker implements IndexNameMaker {
-
-    private final long startSecondTB;
-    private final long endSecondTB;
-    private final String indexName;
-
-    public TimeRangeIndexNameMaker(final String indexName, final long startSecondTB, final long endSecondTB) {
-        this.startSecondTB = startSecondTB;
-        this.endSecondTB = endSecondTB;
-        this.indexName = indexName;
-    }
+@Getter
+@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
+public final class Search {
+    private final Integer from;
+    private final Integer size;
+    private final Query query;
+    private final Sorts sort;
+    private final ImmutableMap<String, Aggregation> aggregations;
 
-    @Override
-    public String[] make() {
-        return TimeSeriesUtils.superDatasetIndexNames(indexName, startSecondTB, endSecondTB);
+    public static SearchBuilder builder() {
+        return new SearchBuilder();
     }
 }
diff --git a/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/SearchBuilder.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/SearchBuilder.java
new file mode 100644
index 0000000..11a442d
--- /dev/null
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/SearchBuilder.java
@@ -0,0 +1,114 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.library.elasticsearch.requests.search;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import org.apache.skywalking.library.elasticsearch.requests.search.aggregation.Aggregation;
+import org.apache.skywalking.library.elasticsearch.requests.search.aggregation.AggregationBuilder;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.Objects.requireNonNull;
+
+public final class SearchBuilder {
+    private Integer from;
+    private Integer size;
+    private Query query;
+    private ImmutableList.Builder<Sort> sort;
+    private ImmutableMap.Builder<String, Aggregation> aggregations;
+
+    SearchBuilder() {
+    }
+
+    public SearchBuilder from(Integer from) {
+        requireNonNull(from, "from");
+        checkArgument(from >= 0, "from must be >= 0, but was %s", from);
+        this.from = from;
+        return this;
+    }
+
+    public SearchBuilder size(Integer size) {
+        requireNonNull(size, "size");
+        checkArgument(size >= 0, "size must be positive, but was %s", size);
+        this.size = size;
+        return this;
+    }
+
+    public SearchBuilder sort(String by, Sort.Order order) {
+        checkArgument(!Strings.isNullOrEmpty(by), "by cannot be blank");
+        requireNonNull(order, "order");
+        sort().add(new Sort(by, order));
+        return this;
+    }
+
+    public SearchBuilder query(Query query) {
+        requireNonNull(query, "query");
+        this.query = query;
+        return this;
+    }
+
+    public SearchBuilder query(QueryBuilder queryBuilder) {
+        return query(queryBuilder.build());
+    }
+
+    public SearchBuilder aggregation(Aggregation aggregation) {
+        requireNonNull(aggregation, "aggregation");
+        aggregations().put(aggregation.name(), aggregation);
+        return this;
+    }
+
+    public SearchBuilder aggregation(AggregationBuilder builder) {
+        requireNonNull(builder, "builder");
+        return aggregation(builder.build());
+    }
+
+    public Search build() {
+        final Sorts sorts;
+        if (sort == null) {
+            sorts = null;
+        } else {
+            sorts = new Sorts(sort.build());
+        }
+
+        final ImmutableMap<String, Aggregation> aggregations;
+        if (this.aggregations == null) {
+            aggregations = null;
+        } else {
+            aggregations = aggregations().build();
+        }
+
+        return new Search(
+            from, size, query, sorts, aggregations
+        );
+    }
+
+    private ImmutableList.Builder<Sort> sort() {
+        if (sort == null) {
+            sort = ImmutableList.builder();
+        }
+        return sort;
+    }
+
+    private ImmutableMap.Builder<String, Aggregation> aggregations() {
+        if (aggregations == null) {
+            aggregations = ImmutableMap.builder();
+        }
+        return aggregations;
+    }
+}
diff --git a/oap-server/server-library/library-client/src/main/java/org/apache/skywalking/oap/server/library/client/grpc/GRPCClientException.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/Sort.java
similarity index 59%
copy from oap-server/server-library/library-client/src/main/java/org/apache/skywalking/oap/server/library/client/grpc/GRPCClientException.java
copy to oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/Sort.java
index 7b00473..e99bf39 100644
--- a/oap-server/server-library/library-client/src/main/java/org/apache/skywalking/oap/server/library/client/grpc/GRPCClientException.java
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/Sort.java
@@ -13,20 +13,32 @@
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
- *
  */
 
-package org.apache.skywalking.oap.server.library.client.grpc;
+package org.apache.skywalking.library.elasticsearch.requests.search;
 
-import org.apache.skywalking.oap.server.library.client.ClientException;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
 
-public class GRPCClientException extends ClientException {
+@Getter
+@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
+public final class Sort {
+    private final String name;
+    private final Order order;
 
-    public GRPCClientException(String message) {
-        super(message);
-    }
+    public enum Order {
+        ASC("asc"), DESC("desc");
+
+        final String value;
+
+        Order(final String value) {
+            this.value = value;
+        }
 
-    public GRPCClientException(String message, Throwable cause) {
-        super(message, cause);
+        @Override
+        public String toString() {
+            return value;
+        }
     }
 }
diff --git a/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/Sorts.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/Sorts.java
new file mode 100644
index 0000000..9088363
--- /dev/null
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/Sorts.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.library.elasticsearch.requests.search;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Iterator;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@Getter
+@JsonSerialize(using = Sorts.Serializer.class)
+@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
+final class Sorts implements Iterable<Sort> {
+    private final ImmutableList<Sort> sorts;
+
+    @Override
+    public Iterator<Sort> iterator() {
+        if (sorts == null) {
+            return Collections.emptyIterator();
+        }
+        return sorts.iterator();
+    }
+
+    static class Serializer extends JsonSerializer<Sorts> {
+        @Override
+        public void serialize(final Sorts value, final JsonGenerator gen,
+                              final SerializerProvider provider)
+            throws IOException {
+
+            gen.writeStartArray();
+            {
+                for (final Sort sort : value) {
+                    gen.writeStartObject();
+                    {
+                        gen.writeFieldName(sort.getName());
+                        gen.writeStartObject();
+                        {
+                            gen.writeStringField("order", sort.getOrder().toString());
+                        }
+                        gen.writeEndObject();
+                    }
+                    gen.writeEndObject();
+                }
+            }
+            gen.writeEndArray();
+        }
+    }
+}
diff --git a/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/TermQuery.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/TermQuery.java
new file mode 100644
index 0000000..3ee142b
--- /dev/null
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/TermQuery.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.library.elasticsearch.requests.search;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import java.io.IOException;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@Getter
+@JsonSerialize(using = TermQuery.Serializer.class)
+@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
+public final class TermQuery extends Query {
+    private final String name;
+    private final Object value;
+
+    static final class Serializer extends JsonSerializer<TermQuery> {
+        static final String NAME = "term";
+
+        @Override
+        public void serialize(final TermQuery term, final JsonGenerator gen,
+                              final SerializerProvider provider) throws IOException {
+            gen.writeStartObject();
+            {
+                gen.writeFieldName(NAME);
+                gen.writeStartObject();
+                {
+                    gen.writeFieldName(term.getName());
+                    gen.writeObject(term.getValue());
+                }
+                gen.writeEndObject();
+            }
+            gen.writeEndObject();
+        }
+    }
+}
diff --git a/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/TermsQuery.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/TermsQuery.java
new file mode 100644
index 0000000..4f5b4cf
--- /dev/null
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/TermsQuery.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.library.elasticsearch.requests.search;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import java.io.IOException;
+import java.util.List;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@Getter
+@JsonSerialize(using = TermsQuery.Serializer.class)
+@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
+public final class TermsQuery extends Query {
+    private final String name;
+    private final List<?> values;
+
+    static final class Serializer extends JsonSerializer<TermsQuery> {
+        static final String NAME = "terms";
+
+        @Override
+        public void serialize(final TermsQuery term, final JsonGenerator gen,
+                              final SerializerProvider provider) throws IOException {
+            gen.writeStartObject();
+            {
+                gen.writeFieldName(NAME);
+                gen.writeStartObject();
+                {
+                    gen.writeFieldName(term.getName());
+                    gen.writeObject(term.getValues());
+                }
+                gen.writeEndObject();
+            }
+            gen.writeEndObject();
+        }
+    }
+}
diff --git a/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/aggregation/Aggregation.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/aggregation/Aggregation.java
new file mode 100644
index 0000000..9e70fce
--- /dev/null
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/aggregation/Aggregation.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.library.elasticsearch.requests.search.aggregation;
+
+import com.google.common.base.Strings;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+public abstract class Aggregation {
+    public abstract String name();
+
+    public static TermsAggregationBuilder terms(String name) {
+        return new TermsAggregationBuilder(name);
+    }
+
+    public static AvgAggregationBuilder avg(String name) {
+        checkArgument(!Strings.isNullOrEmpty(name), "name cannot be blank");
+        return new AvgAggregationBuilder(name);
+    }
+
+    public static MinAggregationBuilder min(String name) {
+        checkArgument(!Strings.isNullOrEmpty(name), "name cannot be blank");
+        return new MinAggregationBuilder(name);
+    }
+
+    public static MaxAggregationBuilder max(String name) {
+        checkArgument(!Strings.isNullOrEmpty(name), "name cannot be blank");
+        return new MaxAggregationBuilder(name);
+    }
+
+    public static SumAggregationBuilder sum(String name) {
+        checkArgument(!Strings.isNullOrEmpty(name), "name cannot be blank");
+        return new SumAggregationBuilder(name);
+    }
+}
diff --git a/oap-server/server-bootstrap/src/main/java/org/apache/skywalking/oap/server/starter/config/ConfigLoader.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/aggregation/AggregationBuilder.java
similarity index 84%
copy from oap-server/server-bootstrap/src/main/java/org/apache/skywalking/oap/server/starter/config/ConfigLoader.java
copy to oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/aggregation/AggregationBuilder.java
index c72d418..732a3b6 100644
--- a/oap-server/server-bootstrap/src/main/java/org/apache/skywalking/oap/server/starter/config/ConfigLoader.java
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/aggregation/AggregationBuilder.java
@@ -13,11 +13,10 @@
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
- *
  */
 
-package org.apache.skywalking.oap.server.starter.config;
+package org.apache.skywalking.library.elasticsearch.requests.search.aggregation;
 
-public interface ConfigLoader<T> {
-    T load() throws ConfigFileNotFoundException;
+public interface AggregationBuilder {
+    Aggregation build();
 }
diff --git a/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/aggregation/AvgAggregation.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/aggregation/AvgAggregation.java
new file mode 100644
index 0000000..4319b40
--- /dev/null
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/aggregation/AvgAggregation.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.library.elasticsearch.requests.search.aggregation;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import java.io.IOException;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@Getter
+@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
+@JsonSerialize(using = AvgAggregation.Serializer.class)
+public final class AvgAggregation extends Aggregation {
+    private final String name;
+    private final String field;
+
+    @Override
+    public String name() {
+        return name;
+    }
+
+    static final class Serializer extends JsonSerializer<AvgAggregation> {
+        @Override
+        public void serialize(final AvgAggregation value, final JsonGenerator gen,
+                              final SerializerProvider provider) throws IOException {
+            gen.writeStartObject();
+            {
+                gen.writeFieldName("avg");
+                gen.writeStartObject();
+                {
+                    gen.writeStringField("field", value.getField());
+                }
+                gen.writeEndObject();
+            }
+            gen.writeEndObject();
+        }
+    }
+}
diff --git a/oap-server/server-storage-plugin/storage-elasticsearch7-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch7/client/ElasticSearch7InsertRequest.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/aggregation/AvgAggregationBuilder.java
similarity index 57%
copy from oap-server/server-storage-plugin/storage-elasticsearch7-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch7/client/ElasticSearch7InsertRequest.java
copy to oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/aggregation/AvgAggregationBuilder.java
index 720a334..53d050c 100644
--- a/oap-server/server-storage-plugin/storage-elasticsearch7-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch7/client/ElasticSearch7InsertRequest.java
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/aggregation/AvgAggregationBuilder.java
@@ -15,22 +15,29 @@
  * limitations under the License.
  */
 
-package org.apache.skywalking.oap.server.storage.plugin.elasticsearch7.client;
+package org.apache.skywalking.library.elasticsearch.requests.search.aggregation;
 
-import org.apache.skywalking.oap.server.library.client.request.InsertRequest;
-import org.elasticsearch.action.index.IndexRequest;
-import org.elasticsearch.common.xcontent.XContentBuilder;
+import com.google.common.base.Strings;
 
-public class ElasticSearch7InsertRequest extends IndexRequest implements InsertRequest {
+import static com.google.common.base.Preconditions.checkArgument;
 
-    public ElasticSearch7InsertRequest(String index, String id) {
-        super(index);
-        id(id);
+public final class AvgAggregationBuilder implements AggregationBuilder {
+    private final String name;
+
+    private String field;
+
+    AvgAggregationBuilder(String name) {
+        this.name = name;
     }
 
-    @Override
-    public ElasticSearch7InsertRequest source(XContentBuilder sourceBuilder) {
-        super.source(sourceBuilder);
+    public AvgAggregationBuilder field(String field) {
+        checkArgument(!Strings.isNullOrEmpty(field), "field cannot be blank");
+        this.field = field;
         return this;
     }
+
+    @Override
+    public AvgAggregation build() {
+        return new AvgAggregation(name, field);
+    }
 }
diff --git a/oap-server/server-library/library-client/src/main/java/org/apache/skywalking/oap/server/library/client/grpc/GRPCClientException.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/aggregation/BucketOrder.java
similarity index 64%
rename from oap-server/server-library/library-client/src/main/java/org/apache/skywalking/oap/server/library/client/grpc/GRPCClientException.java
rename to oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/aggregation/BucketOrder.java
index 7b00473..e7e23f4 100644
--- a/oap-server/server-library/library-client/src/main/java/org/apache/skywalking/oap/server/library/client/grpc/GRPCClientException.java
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/aggregation/BucketOrder.java
@@ -13,20 +13,21 @@
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
- *
  */
 
-package org.apache.skywalking.oap.server.library.client.grpc;
-
-import org.apache.skywalking.oap.server.library.client.ClientException;
+package org.apache.skywalking.library.elasticsearch.requests.search.aggregation;
 
-public class GRPCClientException extends ClientException {
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
 
-    public GRPCClientException(String message) {
-        super(message);
-    }
+@Getter
+@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
+public final class BucketOrder {
+    private final String path;
+    private final boolean asc;
 
-    public GRPCClientException(String message, Throwable cause) {
-        super(message, cause);
+    public static BucketOrder aggregation(String path, boolean asc) {
+        return new BucketOrder(path, asc);
     }
 }
diff --git a/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/aggregation/MaxAggregation.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/aggregation/MaxAggregation.java
new file mode 100644
index 0000000..bc3fa49
--- /dev/null
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/aggregation/MaxAggregation.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.library.elasticsearch.requests.search.aggregation;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import java.io.IOException;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@Getter
+@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
+@JsonSerialize(using = MaxAggregation.Serializer.class)
+public final class MaxAggregation extends Aggregation {
+    private final String name;
+    private final String field;
+
+    @Override
+    public String name() {
+        return name;
+    }
+
+    static final class Serializer extends JsonSerializer<MaxAggregation> {
+        @Override
+        public void serialize(final MaxAggregation value, final JsonGenerator gen,
+                              final SerializerProvider provider) throws IOException {
+            gen.writeStartObject();
+            {
+                gen.writeFieldName("max");
+                gen.writeStartObject();
+                {
+                    gen.writeStringField("field", value.getField());
+                }
+                gen.writeEndObject();
+            }
+            gen.writeEndObject();
+        }
+    }
+}
diff --git a/oap-server/server-storage-plugin/storage-elasticsearch7-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch7/client/ElasticSearch7UpdateRequest.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/aggregation/MaxAggregationBuilder.java
similarity index 57%
rename from oap-server/server-storage-plugin/storage-elasticsearch7-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch7/client/ElasticSearch7UpdateRequest.java
rename to oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/aggregation/MaxAggregationBuilder.java
index f53c8bd..8b74160 100644
--- a/oap-server/server-storage-plugin/storage-elasticsearch7-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch7/client/ElasticSearch7UpdateRequest.java
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/aggregation/MaxAggregationBuilder.java
@@ -15,20 +15,29 @@
  * limitations under the License.
  */
 
-package org.apache.skywalking.oap.server.storage.plugin.elasticsearch7.client;
+package org.apache.skywalking.library.elasticsearch.requests.search.aggregation;
 
-import org.elasticsearch.action.update.UpdateRequest;
-import org.elasticsearch.common.xcontent.XContentBuilder;
+import com.google.common.base.Strings;
 
-public class ElasticSearch7UpdateRequest extends UpdateRequest implements org.apache.skywalking.oap.server.library.client.request.UpdateRequest {
+import static com.google.common.base.Preconditions.checkArgument;
 
-    public ElasticSearch7UpdateRequest(String index, String id) {
-        super(index, id);
+public final class MaxAggregationBuilder implements AggregationBuilder {
+    private final String name;
+
+    private String field;
+
+    MaxAggregationBuilder(String name) {
+        this.name = name;
     }
 
-    @Override
-    public ElasticSearch7UpdateRequest doc(XContentBuilder source) {
-        super.doc(source);
+    public MaxAggregationBuilder field(String field) {
+        checkArgument(!Strings.isNullOrEmpty(field), "field cannot be blank");
+        this.field = field;
         return this;
     }
+
+    @Override
+    public MaxAggregation build() {
+        return new MaxAggregation(name, field);
+    }
 }
diff --git a/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/aggregation/MinAggregation.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/aggregation/MinAggregation.java
new file mode 100644
index 0000000..cc7d58f
--- /dev/null
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/aggregation/MinAggregation.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.library.elasticsearch.requests.search.aggregation;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import java.io.IOException;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@Getter
+@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
+@JsonSerialize(using = MinAggregation.Serializer.class)
+public final class MinAggregation extends Aggregation {
+    private final String name;
+    private final String field;
+
+    @Override
+    public String name() {
+        return name;
+    }
+
+    static final class Serializer extends JsonSerializer<MinAggregation> {
+        @Override
+        public void serialize(final MinAggregation value, final JsonGenerator gen,
+                              final SerializerProvider provider) throws IOException {
+            gen.writeStartObject();
+            {
+                gen.writeFieldName("min");
+                gen.writeStartObject();
+                {
+                    gen.writeStringField("field", value.getField());
+                }
+                gen.writeEndObject();
+            }
+            gen.writeEndObject();
+        }
+    }
+}
diff --git a/oap-server/server-storage-plugin/storage-elasticsearch7-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch7/client/ElasticSearch7InsertRequest.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/aggregation/MinAggregationBuilder.java
similarity index 57%
copy from oap-server/server-storage-plugin/storage-elasticsearch7-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch7/client/ElasticSearch7InsertRequest.java
copy to oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/aggregation/MinAggregationBuilder.java
index 720a334..02e2c9e 100644
--- a/oap-server/server-storage-plugin/storage-elasticsearch7-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch7/client/ElasticSearch7InsertRequest.java
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/aggregation/MinAggregationBuilder.java
@@ -15,22 +15,29 @@
  * limitations under the License.
  */
 
-package org.apache.skywalking.oap.server.storage.plugin.elasticsearch7.client;
+package org.apache.skywalking.library.elasticsearch.requests.search.aggregation;
 
-import org.apache.skywalking.oap.server.library.client.request.InsertRequest;
-import org.elasticsearch.action.index.IndexRequest;
-import org.elasticsearch.common.xcontent.XContentBuilder;
+import com.google.common.base.Strings;
 
-public class ElasticSearch7InsertRequest extends IndexRequest implements InsertRequest {
+import static com.google.common.base.Preconditions.checkArgument;
 
-    public ElasticSearch7InsertRequest(String index, String id) {
-        super(index);
-        id(id);
+public final class MinAggregationBuilder implements AggregationBuilder {
+    private final String name;
+
+    private String field;
+
+    MinAggregationBuilder(String name) {
+        this.name = name;
     }
 
-    @Override
-    public ElasticSearch7InsertRequest source(XContentBuilder sourceBuilder) {
-        super.source(sourceBuilder);
+    public MinAggregationBuilder field(String field) {
+        checkArgument(!Strings.isNullOrEmpty(field), "field cannot be blank");
+        this.field = field;
         return this;
     }
+
+    @Override
+    public MinAggregation build() {
+        return new MinAggregation(name, field);
+    }
 }
diff --git a/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/aggregation/SumAggregation.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/aggregation/SumAggregation.java
new file mode 100644
index 0000000..5d90144
--- /dev/null
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/aggregation/SumAggregation.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.library.elasticsearch.requests.search.aggregation;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import java.io.IOException;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@Getter
+@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
+@JsonSerialize(using = SumAggregation.Serializer.class)
+public final class SumAggregation extends Aggregation {
+    private final String name;
+    private final String field;
+
+    @Override
+    public String name() {
+        return name;
+    }
+
+    static final class Serializer extends JsonSerializer<SumAggregation> {
+        @Override
+        public void serialize(final SumAggregation value, final JsonGenerator gen,
+                              final SerializerProvider provider) throws IOException {
+            gen.writeStartObject();
+            {
+                gen.writeFieldName("sum");
+                gen.writeStartObject();
+                {
+                    gen.writeStringField("field", value.getField());
+                }
+                gen.writeEndObject();
+            }
+            gen.writeEndObject();
+        }
+    }
+}
diff --git a/oap-server/server-storage-plugin/storage-elasticsearch7-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch7/client/ElasticSearch7InsertRequest.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/aggregation/SumAggregationBuilder.java
similarity index 57%
rename from oap-server/server-storage-plugin/storage-elasticsearch7-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch7/client/ElasticSearch7InsertRequest.java
rename to oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/aggregation/SumAggregationBuilder.java
index 720a334..b7a7f88 100644
--- a/oap-server/server-storage-plugin/storage-elasticsearch7-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch7/client/ElasticSearch7InsertRequest.java
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/aggregation/SumAggregationBuilder.java
@@ -15,22 +15,29 @@
  * limitations under the License.
  */
 
-package org.apache.skywalking.oap.server.storage.plugin.elasticsearch7.client;
+package org.apache.skywalking.library.elasticsearch.requests.search.aggregation;
 
-import org.apache.skywalking.oap.server.library.client.request.InsertRequest;
-import org.elasticsearch.action.index.IndexRequest;
-import org.elasticsearch.common.xcontent.XContentBuilder;
+import com.google.common.base.Strings;
 
-public class ElasticSearch7InsertRequest extends IndexRequest implements InsertRequest {
+import static com.google.common.base.Preconditions.checkArgument;
 
-    public ElasticSearch7InsertRequest(String index, String id) {
-        super(index);
-        id(id);
+public final class SumAggregationBuilder implements AggregationBuilder {
+    private final String name;
+
+    private String field;
+
+    SumAggregationBuilder(String name) {
+        this.name = name;
     }
 
-    @Override
-    public ElasticSearch7InsertRequest source(XContentBuilder sourceBuilder) {
-        super.source(sourceBuilder);
+    public SumAggregationBuilder field(String field) {
+        checkArgument(!Strings.isNullOrEmpty(field), "field cannot be blank");
+        this.field = field;
         return this;
     }
+
+    @Override
+    public MaxAggregation build() {
+        return new MaxAggregation(name, field);
+    }
 }
diff --git a/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/aggregation/TermsAggregation.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/aggregation/TermsAggregation.java
new file mode 100644
index 0000000..0cb72f7
--- /dev/null
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/aggregation/TermsAggregation.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.library.elasticsearch.requests.search.aggregation;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.google.common.collect.ImmutableMap;
+import java.io.IOException;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@Getter
+@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
+@JsonSerialize(using = TermsAggregation.Serializer.class)
+public final class TermsAggregation extends Aggregation {
+    private final String name;
+    private final String field;
+    private final BucketOrder order;
+    private final Integer size;
+    private final ImmutableMap<String, Aggregation> aggregations;
+
+    @Override
+    public String name() {
+        return name;
+    }
+
+    static final class Serializer extends JsonSerializer<TermsAggregation> {
+        @Override
+        public void serialize(final TermsAggregation value, final JsonGenerator gen,
+                              final SerializerProvider provider) throws IOException {
+            gen.writeStartObject();
+            {
+                gen.writeFieldName("terms");
+                gen.writeStartObject();
+                {
+                    gen.writeStringField("field", value.getField());
+                }
+                gen.writeEndObject();
+
+                if (value.getAggregations() != null && !value.getAggregations().isEmpty()) {
+                    gen.writeObjectField("aggregations", value.getAggregations());
+                }
+            }
+            gen.writeEndObject();
+        }
+    }
+}
diff --git a/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/aggregation/TermsAggregationBuilder.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/aggregation/TermsAggregationBuilder.java
new file mode 100644
index 0000000..3c3ccbc
--- /dev/null
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/requests/search/aggregation/TermsAggregationBuilder.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.library.elasticsearch.requests.search.aggregation;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableMap;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.Objects.requireNonNull;
+
+public final class TermsAggregationBuilder implements AggregationBuilder {
+    private final String name;
+
+    private String field;
+    private BucketOrder order;
+    private Integer size;
+    private ImmutableMap.Builder<String, Aggregation> subAggregations;
+
+    TermsAggregationBuilder(final String name) {
+        checkArgument(!Strings.isNullOrEmpty(name), "name cannot be blank");
+        this.name = name;
+    }
+
+    public TermsAggregationBuilder field(String field) {
+        checkArgument(!Strings.isNullOrEmpty(field), "field cannot be blank");
+        this.field = field;
+        return this;
+    }
+
+    public TermsAggregationBuilder order(BucketOrder order) {
+        requireNonNull(order, "order");
+        this.order = order;
+        return this;
+    }
+
+    public TermsAggregationBuilder size(int size) {
+        checkArgument(size >= 0, "size must be >= 0");
+        this.size = size;
+        return this;
+    }
+
+    public TermsAggregationBuilder subAggregation(Aggregation subAggregation) {
+        requireNonNull(subAggregation, "subAggregation");
+        subAggregations().put(subAggregation.name(), subAggregation);
+        return this;
+    }
+
+    public TermsAggregationBuilder subAggregation(AggregationBuilder subAggregationBuilder) {
+        requireNonNull(subAggregationBuilder, "subAggregationBuilder");
+        return subAggregation(subAggregationBuilder.build());
+    }
+
+    @Override
+    public TermsAggregation build() {
+        ImmutableMap<String, Aggregation> subAggregations;
+        if (this.subAggregations == null) {
+            subAggregations = null;
+        } else {
+            subAggregations = this.subAggregations.build();
+        }
+        return new TermsAggregation(
+            name, field, order, size, subAggregations
+        );
+    }
+
+    private ImmutableMap.Builder<String, Aggregation> subAggregations() {
+        if (subAggregations == null) {
+            subAggregations = ImmutableMap.builder();
+        }
+        return subAggregations;
+    }
+}
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/cluster/ServiceRegisterException.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/response/Document.java
similarity index 70%
copy from oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/cluster/ServiceRegisterException.java
copy to oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/response/Document.java
index edb47d3..89e0565 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/cluster/ServiceRegisterException.java
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/response/Document.java
@@ -13,14 +13,21 @@
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
- *
  */
 
-package org.apache.skywalking.oap.server.core.cluster;
+package org.apache.skywalking.library.elasticsearch.response;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.Map;
+import lombok.Data;
+
+@Data
+public final class Document {
+    private boolean found;
 
-public class ServiceRegisterException extends RuntimeException {
+    @JsonProperty("_id")
+    private String id;
 
-    public ServiceRegisterException(String message) {
-        super(message);
-    }
+    @JsonProperty("_source")
+    private Map<String, Object> source;
 }
diff --git a/oap-server/server-storage-plugin/storage-elasticsearch7-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch7/query/MetadataQueryEs7DAO.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/response/Documents.java
similarity index 62%
rename from oap-server/server-storage-plugin/storage-elasticsearch7-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch7/query/MetadataQueryEs7DAO.java
rename to oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/response/Documents.java
index 89813d4..1a7a422 100644
--- a/oap-server/server-storage-plugin/storage-elasticsearch7-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch7/query/MetadataQueryEs7DAO.java
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/response/Documents.java
@@ -13,17 +13,24 @@
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
- *
  */
 
-package org.apache.skywalking.oap.server.storage.plugin.elasticsearch7.query;
+package org.apache.skywalking.library.elasticsearch.response;
 
-import org.apache.skywalking.oap.server.library.client.elasticsearch.ElasticSearchClient;
-import org.apache.skywalking.oap.server.storage.plugin.elasticsearch.query.MetadataQueryEsDAO;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import lombok.Setter;
 
-public class MetadataQueryEs7DAO extends MetadataQueryEsDAO {
+public final class Documents implements Iterable<Document> {
+    @Setter
+    private List<Document> docs;
 
-    public MetadataQueryEs7DAO(final ElasticSearchClient client, final int queryMaxSize) {
-        super(client, queryMaxSize);
+    @Override
+    public Iterator<Document> iterator() {
+        if (docs == null) {
+            return Collections.emptyIterator();
+        }
+        return docs.stream().filter(Document::isFound).iterator();
     }
 }
diff --git a/oap-server/server-library/library-client/src/test/java/org/apache/skywalking/oap/server/library/client/elasticsearch/ITElasticSearchClientOfNamespace.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/response/Index.java
similarity index 76%
copy from oap-server/server-library/library-client/src/test/java/org/apache/skywalking/oap/server/library/client/elasticsearch/ITElasticSearchClientOfNamespace.java
copy to oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/response/Index.java
index 7960299..10032b7 100644
--- a/oap-server/server-library/library-client/src/test/java/org/apache/skywalking/oap/server/library/client/elasticsearch/ITElasticSearchClientOfNamespace.java
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/response/Index.java
@@ -13,14 +13,16 @@
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
- *
  */
 
-package org.apache.skywalking.oap.server.library.client.elasticsearch;
+package org.apache.skywalking.library.elasticsearch.response;
 
-public class ITElasticSearchClientOfNamespace extends ITElasticSearchClient {
+import java.util.Map;
+import lombok.Data;
 
-    public ITElasticSearchClientOfNamespace() {
-        super("test");
-    }
+@Data
+public final class Index {
+    private Map<String, Object> settings;
+    private Mappings mappings;
+    private Map<String, Object> aliases;
 }
diff --git a/oap-server/server-library/library-client/src/test/java/org/apache/skywalking/oap/server/library/client/elasticsearch/ITElasticSearchClientOfNamespace.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/response/IndexTemplate.java
similarity index 68%
copy from oap-server/server-library/library-client/src/test/java/org/apache/skywalking/oap/server/library/client/elasticsearch/ITElasticSearchClientOfNamespace.java
copy to oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/response/IndexTemplate.java
index 7960299..169d9ba 100644
--- a/oap-server/server-library/library-client/src/test/java/org/apache/skywalking/oap/server/library/client/elasticsearch/ITElasticSearchClientOfNamespace.java
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/response/IndexTemplate.java
@@ -13,14 +13,20 @@
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
- *
  */
 
-package org.apache.skywalking.oap.server.library.client.elasticsearch;
+package org.apache.skywalking.library.elasticsearch.response;
 
-public class ITElasticSearchClientOfNamespace extends ITElasticSearchClient {
+import java.util.List;
+import java.util.Map;
+import lombok.Data;
 
-    public ITElasticSearchClientOfNamespace() {
-        super("test");
-    }
+@Data
+public final class IndexTemplate {
+    private String name;
+    private int order;
+    private List<String> indexPatterns;
+    private Map<String, Object> settings;
+    private Mappings mappings;
+    private Map<String, Object> aliases;
 }
diff --git a/oap-server/server-storage-plugin/storage-elasticsearch7-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch7/base/IndexEs7Structures.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/response/IndexTemplates.java
similarity index 53%
copy from oap-server/server-storage-plugin/storage-elasticsearch7-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch7/base/IndexEs7Structures.java
copy to oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/response/IndexTemplates.java
index 0b2501d..35b27bd 100644
--- a/oap-server/server-storage-plugin/storage-elasticsearch7-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch7/base/IndexEs7Structures.java
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/response/IndexTemplates.java
@@ -13,28 +13,33 @@
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
- *
  */
 
-package org.apache.skywalking.oap.server.storage.plugin.elasticsearch7.base;
+package org.apache.skywalking.library.elasticsearch.response;
 
-import java.util.HashMap;
+import java.util.Collections;
+import java.util.Iterator;
 import java.util.Map;
-import org.apache.skywalking.oap.server.storage.plugin.elasticsearch.base.IndexStructures;
+import java.util.Optional;
+import lombok.Data;
 
-public class IndexEs7Structures extends IndexStructures {
+@Data
+public final class IndexTemplates implements Iterable<IndexTemplate> {
+    private Map<String, IndexTemplate> templates;
 
-    @Override
-    protected PropertiesExtractor doGetPropertiesExtractor() {
-        return mapping -> (Map<String, Object>) mapping.get("properties");
+    public Optional<IndexTemplate> get(String name) {
+        final Map<String, IndexTemplate> templates = getTemplates();
+        if (templates == null) {
+            return Optional.empty();
+        }
+        return Optional.ofNullable(templates.get(name));
     }
 
     @Override
-    protected PropertiesWrapper doGetPropertiesWrapper() {
-        return properties -> {
-            HashMap<String, Object> mappings = new HashMap<>();
-            mappings.put("properties", properties);
-            return mappings;
-        };
+    public Iterator<IndexTemplate> iterator() {
+        if (getTemplates() == null) {
+            return Collections.emptyIterator();
+        }
+        return getTemplates().values().iterator();
     }
 }
diff --git a/oap-server/server-library/library-client/src/main/java/org/apache/skywalking/oap/server/library/client/elasticsearch/ElasticSearchUpdateRequest.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/response/Mappings.java
similarity index 58%
rename from oap-server/server-library/library-client/src/main/java/org/apache/skywalking/oap/server/library/client/elasticsearch/ElasticSearchUpdateRequest.java
rename to oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/response/Mappings.java
index 2663856..642dd88 100644
--- a/oap-server/server-library/library-client/src/main/java/org/apache/skywalking/oap/server/library/client/elasticsearch/ElasticSearchUpdateRequest.java
+++ b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/response/Mappings.java
@@ -15,20 +15,29 @@
  * limitations under the License.
  */
 
-package org.apache.skywalking.oap.server.library.client.elasticsearch;
+package org.apache.skywalking.library.elasticsearch.response;
 
-import org.elasticsearch.action.update.UpdateRequest;
-import org.elasticsearch.common.xcontent.XContentBuilder;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import java.util.HashMap;
+import java.util.Map;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.ToString;
 
-public class ElasticSearchUpdateRequest extends UpdateRequest implements org.apache.skywalking.oap.server.library.client.request.UpdateRequest {
+@Builder
+@ToString
+@NoArgsConstructor // For deserialization
+@AllArgsConstructor
+public final class Mappings {
+    @Getter
+    @Setter
+    @JsonIgnore
+    private String type;
 
-    public ElasticSearchUpdateRequest(String index, String type, String id) {
-        super(index, type, id);
-    }
-
-    @Override
-    public ElasticSearchUpdateRequest doc(XContentBuilder source) {
-        super.doc(source);
-        return this;
-    }
+    @Getter
+    @Setter
+    private Map<String, Object> properties = new HashMap<>();
 }
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/cluster/ServiceRegisterException.java b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/response/NodeInfo.java
... 8466 lines suppressed ...