diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/pom.xml b/spring-boot-project/spring-boot-actuator-autoconfigure/pom.xml
index 40e3ab6025..9eb4274d76 100644
--- a/spring-boot-project/spring-boot-actuator-autoconfigure/pom.xml
+++ b/spring-boot-project/spring-boot-actuator-autoconfigure/pom.xml
@@ -265,8 +265,8 @@
true
- org.elasticsearch
- elasticsearch
+ org.elasticsearch.client
+ elasticsearch-rest-client
true
diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticSearchRestHealthIndicatorAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticSearchRestHealthIndicatorAutoConfiguration.java
new file mode 100644
index 0000000000..c47b934e6d
--- /dev/null
+++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticSearchRestHealthIndicatorAutoConfiguration.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2012-2018 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.springframework.boot.actuate.autoconfigure.elasticsearch;
+
+import java.util.Map;
+
+import org.elasticsearch.client.RestClient;
+
+import org.springframework.boot.actuate.autoconfigure.health.CompositeHealthIndicatorConfiguration;
+import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator;
+import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration;
+import org.springframework.boot.actuate.elasticsearch.ElasticsearchRestHealthIndicator;
+import org.springframework.boot.actuate.health.HealthIndicator;
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+import org.springframework.boot.autoconfigure.AutoConfigureBefore;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.elasticsearch.rest.RestClientAutoConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * {@link EnableAutoConfiguration Auto-configuration} for
+ * {@link ElasticsearchRestHealthIndicator} using the {@link RestClient}.
+ *
+ * @author Artsiom Yudovin
+ * @since 2.1.0
+ */
+
+@Configuration
+@ConditionalOnClass(RestClient.class)
+@ConditionalOnBean(RestClient.class)
+@ConditionalOnEnabledHealthIndicator("elasticsearch")
+@AutoConfigureBefore(HealthIndicatorAutoConfiguration.class)
+@AutoConfigureAfter({ RestClientAutoConfiguration.class,
+ ElasticSearchClientHealthIndicatorAutoConfiguration.class })
+public class ElasticSearchRestHealthIndicatorAutoConfiguration extends
+ CompositeHealthIndicatorConfiguration {
+
+ private final Map clients;
+
+ public ElasticSearchRestHealthIndicatorAutoConfiguration(
+ Map clients) {
+ this.clients = clients;
+ }
+
+ @Bean
+ @ConditionalOnMissingBean(name = "elasticsearchRestHealthIndicator")
+ public HealthIndicator elasticsearchRestHealthIndicator() {
+ return createHealthIndicator(this.clients);
+ }
+
+ @Override
+ protected ElasticsearchRestHealthIndicator createHealthIndicator(RestClient client) {
+ return new ElasticsearchRestHealthIndicator(client);
+ }
+
+}
diff --git a/spring-boot-project/spring-boot-actuator/pom.xml b/spring-boot-project/spring-boot-actuator/pom.xml
index de7fa8560c..392a8cdabf 100644
--- a/spring-boot-project/spring-boot-actuator/pom.xml
+++ b/spring-boot-project/spring-boot-actuator/pom.xml
@@ -77,6 +77,11 @@
jest
true
+
+ org.elasticsearch.client
+ elasticsearch-rest-client
+ true
+
io.undertow
undertow-servlet
@@ -338,5 +343,6 @@
jsonassert
test
+
diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/elasticsearch/ElasticsearchRestHealthIndicator.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/elasticsearch/ElasticsearchRestHealthIndicator.java
new file mode 100644
index 0000000000..fb542496af
--- /dev/null
+++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/elasticsearch/ElasticsearchRestHealthIndicator.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2012-2018 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.springframework.boot.actuate.elasticsearch;
+
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParser;
+import org.apache.http.HttpStatus;
+import org.elasticsearch.client.Request;
+import org.elasticsearch.client.Response;
+import org.elasticsearch.client.RestClient;
+
+import org.springframework.boot.actuate.health.AbstractHealthIndicator;
+import org.springframework.boot.actuate.health.Health;
+import org.springframework.boot.actuate.health.HealthIndicator;
+
+/**
+ * {@link HealthIndicator} for an Elasticsearch cluster by REST.
+ *
+ * @author Artsiom Yudovin
+ * @since 2.1.0
+ */
+public class ElasticsearchRestHealthIndicator extends AbstractHealthIndicator {
+
+ private final RestClient client;
+
+ private final JsonParser jsonParser = new JsonParser();
+
+ public ElasticsearchRestHealthIndicator(RestClient client) {
+ super("Elasticsearch health check failed");
+ this.client = client;
+ }
+
+ @Override
+ protected void doHealthCheck(Health.Builder builder) throws Exception {
+ Response response = this.client
+ .performRequest(new Request("GET", "/_cluster/health/"));
+
+ if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
+ builder.down();
+ }
+ else {
+ try (InputStreamReader reader = new InputStreamReader(
+ response.getEntity().getContent(), StandardCharsets.UTF_8)) {
+ JsonElement root = this.jsonParser.parse(reader);
+ JsonElement status = root.getAsJsonObject().get("status");
+ if (status.getAsString()
+ .equals(io.searchbox.cluster.Health.Status.RED.getKey())) {
+ builder.outOfService();
+ }
+ else {
+ builder.up();
+ }
+ }
+ }
+ }
+
+}
diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/elasticsearch/ElasticsearchRestHealthIndicatorTest.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/elasticsearch/ElasticsearchRestHealthIndicatorTest.java
new file mode 100644
index 0000000000..67caab025a
--- /dev/null
+++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/elasticsearch/ElasticsearchRestHealthIndicatorTest.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2012-2018 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.springframework.boot.actuate.elasticsearch;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+
+import org.apache.http.StatusLine;
+import org.apache.http.entity.BasicHttpEntity;
+import org.elasticsearch.client.Request;
+import org.elasticsearch.client.Response;
+import org.elasticsearch.client.RestClient;
+import org.junit.Test;
+
+import org.springframework.boot.actuate.health.Status;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.BDDMockito.mock;
+import static org.mockito.BDDMockito.when;
+
+/**
+ * Tests for {@link ElasticsearchRestHealthIndicator}.
+ *
+ * @author Artsiom Yudovin
+ */
+public class ElasticsearchRestHealthIndicatorTest {
+
+ private final RestClient restClient = mock(RestClient.class);
+
+ private final ElasticsearchRestHealthIndicator elasticsearchRestHealthIndicator = new ElasticsearchRestHealthIndicator(
+ this.restClient);
+
+ @Test
+ public void elasticsearchIsUp() throws IOException {
+ BasicHttpEntity httpEntity = new BasicHttpEntity();
+ httpEntity.setContent(
+ new ByteArrayInputStream(createJsonResult(200, "green").getBytes()));
+
+ Response response = mock(Response.class);
+ StatusLine statusLine = mock(StatusLine.class);
+
+ when(statusLine.getStatusCode()).thenReturn(200);
+ when(response.getStatusLine()).thenReturn(statusLine);
+ when(response.getEntity()).thenReturn(httpEntity);
+ when(this.restClient.performRequest(any(Request.class))).thenReturn(response);
+
+ assertThat(this.elasticsearchRestHealthIndicator.health().getStatus())
+ .isEqualTo(Status.UP);
+ }
+
+ @Test
+ public void elasticsearchIsDown() throws IOException {
+ when(this.restClient.performRequest(any(Request.class)))
+ .thenThrow(new IOException("Couldn't connect"));
+
+ assertThat(this.elasticsearchRestHealthIndicator.health().getStatus())
+ .isEqualTo(Status.DOWN);
+ }
+
+ @Test
+ public void elasticsearchIsDownByResponseCode() throws IOException {
+
+ Response response = mock(Response.class);
+ StatusLine statusLine = mock(StatusLine.class);
+
+ when(statusLine.getStatusCode()).thenReturn(500);
+ when(response.getStatusLine()).thenReturn(statusLine);
+ when(this.restClient.performRequest(any(Request.class))).thenReturn(response);
+
+ assertThat(this.elasticsearchRestHealthIndicator.health().getStatus())
+ .isEqualTo(Status.DOWN);
+ }
+
+ @Test
+ public void elasticsearchIsOutOfServiceByStatus() throws IOException {
+ BasicHttpEntity httpEntity = new BasicHttpEntity();
+ httpEntity.setContent(
+ new ByteArrayInputStream(createJsonResult(200, "red").getBytes()));
+
+ Response response = mock(Response.class);
+ StatusLine statusLine = mock(StatusLine.class);
+
+ when(statusLine.getStatusCode()).thenReturn(200);
+ when(response.getStatusLine()).thenReturn(statusLine);
+ when(response.getEntity()).thenReturn(httpEntity);
+ when(this.restClient.performRequest(any(Request.class))).thenReturn(response);
+
+ assertThat(this.elasticsearchRestHealthIndicator.health().getStatus())
+ .isEqualTo(Status.OUT_OF_SERVICE);
+ }
+
+ private String createJsonResult(int responseCode, String status) {
+ String json;
+ if (responseCode == 200) {
+ json = String.format("{\"cluster_name\":\"elasticsearch\","
+ + "\"status\":\"%s\",\"timed_out\":false,\"number_of_nodes\":1,"
+ + "\"number_of_data_nodes\":1,\"active_primary_shards\":0,"
+ + "\"active_shards\":0,\"relocating_shards\":0,\"initializing_shards\":0,"
+ + "\"unassigned_shards\":0,\"delayed_unassigned_shards\":0,"
+ + "\"number_of_pending_tasks\":0,\"number_of_in_flight_fetch\":0,"
+ + "\"task_max_waiting_in_queue_millis\":0,\"active_shards_percent_as_number\":100.0}",
+ status);
+ }
+ else {
+ json = "{\n" + " \"error\": \"Server Error\",\n" + " \"status\": "
+ + responseCode + "\n" + "}";
+ }
+
+ return json;
+ }
+
+}