diff --git a/spring-boot-admin/README.md b/spring-boot-admin/README.md
new file mode 100644
index 0000000000..4cea3f0611
--- /dev/null
+++ b/spring-boot-admin/README.md
@@ -0,0 +1 @@
+Spring Boot Admin
\ No newline at end of file
diff --git a/spring-boot-admin/spring-boot-admin-client/.gitignore b/spring-boot-admin/spring-boot-admin-client/.gitignore
new file mode 100644
index 0000000000..2af7cefb0a
--- /dev/null
+++ b/spring-boot-admin/spring-boot-admin-client/.gitignore
@@ -0,0 +1,24 @@
+target/
+!.mvn/wrapper/maven-wrapper.jar
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+nbproject/private/
+build/
+nbbuild/
+dist/
+nbdist/
+.nb-gradle/
\ No newline at end of file
diff --git a/spring-boot-admin/spring-boot-admin-client/pom.xml b/spring-boot-admin/spring-boot-admin-client/pom.xml
new file mode 100644
index 0000000000..ecb6c3f8b6
--- /dev/null
+++ b/spring-boot-admin/spring-boot-admin-client/pom.xml
@@ -0,0 +1,71 @@
+
+
+ 4.0.0
+
+ com.baeldung
+ spring-boot-admin-client
+ 0.0.1-SNAPSHOT
+ jar
+
+ spring-boot-admin-client
+ Demo project for Spring Boot
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 1.5.8.RELEASE
+
+
+
+
+ UTF-8
+ UTF-8
+ 1.8
+ 1.5.4
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+ de.codecentric
+ spring-boot-admin-starter-client
+ ${spring-boot-admin-starter-client.version}
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.springframework.security
+ spring-security-test
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ build-info
+
+
+
+
+
+
+
+
+
diff --git a/spring-boot-admin/spring-boot-admin-client/src/main/java/com/baeldung/springbootadminclient/SpringBootAdminClientApplication.java b/spring-boot-admin/spring-boot-admin-client/src/main/java/com/baeldung/springbootadminclient/SpringBootAdminClientApplication.java
new file mode 100644
index 0000000000..596da131a6
--- /dev/null
+++ b/spring-boot-admin/spring-boot-admin-client/src/main/java/com/baeldung/springbootadminclient/SpringBootAdminClientApplication.java
@@ -0,0 +1,12 @@
+package com.baeldung.springbootadminclient;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class SpringBootAdminClientApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(SpringBootAdminClientApplication.class, args);
+ }
+}
diff --git a/spring-boot-admin/spring-boot-admin-client/src/main/resources/application.properties b/spring-boot-admin/spring-boot-admin-client/src/main/resources/application.properties
new file mode 100644
index 0000000000..58c178ecd9
--- /dev/null
+++ b/spring-boot-admin/spring-boot-admin-client/src/main/resources/application.properties
@@ -0,0 +1,16 @@
+#basic auth creddentials
+security.user.name=client
+security.user.password=client
+
+#configs to connect to a secured server
+spring.boot.admin.url=http://localhost:8080
+spring.boot.admin.username=admin
+spring.boot.admin.password=admin
+
+#configs to give secured server info
+spring.boot.admin.client.metadata.user.name=${security.user.name}
+spring.boot.admin.client.metadata.user.password=${security.user.password}
+
+#app config
+spring.application.name=spring-boot-admin-client
+server.port=8081
\ No newline at end of file
diff --git a/spring-boot-admin/spring-boot-admin-client/src/main/resources/logback.xml b/spring-boot-admin/spring-boot-admin-client/src/main/resources/logback.xml
new file mode 100644
index 0000000000..ff96acae79
--- /dev/null
+++ b/spring-boot-admin/spring-boot-admin-client/src/main/resources/logback.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+ %date [%thread] %-5level %logger{25} - %msg%n
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/spring-boot-admin/spring-boot-admin-client/src/test/java/com/baeldung/springbootadminclient/SpringBootAdminClientApplicationTests.java b/spring-boot-admin/spring-boot-admin-client/src/test/java/com/baeldung/springbootadminclient/SpringBootAdminClientApplicationTests.java
new file mode 100644
index 0000000000..d70fb1c7cf
--- /dev/null
+++ b/spring-boot-admin/spring-boot-admin-client/src/test/java/com/baeldung/springbootadminclient/SpringBootAdminClientApplicationTests.java
@@ -0,0 +1,55 @@
+package com.baeldung.springbootadminclient;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.core.env.Environment;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.springframework.web.context.WebApplicationContext;
+
+import static org.junit.Assert.assertEquals;
+import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
+import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(webEnvironment = RANDOM_PORT)
+public class SpringBootAdminClientApplicationTests {
+
+ @Autowired Environment environment;
+
+ @Autowired WebApplicationContext wac;
+
+ private MockMvc mockMvc;
+
+ @Before
+ public void setup() {
+ mockMvc = MockMvcBuilders
+ .webAppContextSetup(wac)
+ .build();
+ }
+
+ @Test
+ public void whenEnvironmentAvailable_ThenAdminServerPropertiesExist() {
+ assertEquals(environment.getProperty("spring.boot.admin.url"), "http://localhost:8080");
+ assertEquals(environment.getProperty("spring.boot.admin.username"), "admin");
+ assertEquals(environment.getProperty("spring.boot.admin.password"), "admin");
+ }
+
+ @Test
+ public void whenHttpBasicAttempted_ThenSuccess() throws Exception {
+ mockMvc.perform(get("/env").with(httpBasic("client", "client")));
+ }
+
+ @Test
+ public void whenInvalidHttpBasicAttempted_ThenUnauthorized() throws Exception {
+ mockMvc
+ .perform(get("/env").with(httpBasic("client", "invalid")))
+ .andExpect(status().isUnauthorized());
+ }
+}
diff --git a/spring-boot-admin/spring-boot-admin-server/.gitignore b/spring-boot-admin/spring-boot-admin-server/.gitignore
new file mode 100644
index 0000000000..2af7cefb0a
--- /dev/null
+++ b/spring-boot-admin/spring-boot-admin-server/.gitignore
@@ -0,0 +1,24 @@
+target/
+!.mvn/wrapper/maven-wrapper.jar
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+nbproject/private/
+build/
+nbbuild/
+dist/
+nbdist/
+.nb-gradle/
\ No newline at end of file
diff --git a/spring-boot-admin/spring-boot-admin-server/pom.xml b/spring-boot-admin/spring-boot-admin-server/pom.xml
new file mode 100644
index 0000000000..b199e63b31
--- /dev/null
+++ b/spring-boot-admin/spring-boot-admin-server/pom.xml
@@ -0,0 +1,95 @@
+
+
+ 4.0.0
+
+ com.baeldung
+ spring-boot-admin-server
+ 0.0.1-SNAPSHOT
+ jar
+
+ spring-boot-admin-server
+ Demo project for Spring Boot
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 1.5.8.RELEASE
+
+
+
+
+ UTF-8
+ UTF-8
+ 1.8
+ 1.5.4
+ 1.5.4
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+
+
+ de.codecentric
+ spring-boot-admin-server
+ ${spring-boot-admin-server.version}
+
+
+ de.codecentric
+ spring-boot-admin-server-ui
+ ${spring-boot-admin-server.version}
+
+
+
+
+ de.codecentric
+ spring-boot-admin-server-ui-login
+ ${spring-boot-admin-server.version}
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+ com.hazelcast
+ hazelcast
+
+
+
+ de.codecentric
+ spring-boot-admin-starter-client
+ ${spring-boot-admin-starter-client.version}
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.springframework.security
+ spring-security-test
+ test
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
+
diff --git a/spring-boot-admin/spring-boot-admin-server/src/main/java/com/baeldung/springbootadminserver/SpringBootAdminServerApplication.java b/spring-boot-admin/spring-boot-admin-server/src/main/java/com/baeldung/springbootadminserver/SpringBootAdminServerApplication.java
new file mode 100644
index 0000000000..d1fb4e769b
--- /dev/null
+++ b/spring-boot-admin/spring-boot-admin-server/src/main/java/com/baeldung/springbootadminserver/SpringBootAdminServerApplication.java
@@ -0,0 +1,14 @@
+package com.baeldung.springbootadminserver;
+
+import de.codecentric.boot.admin.config.EnableAdminServer;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@EnableAdminServer
+@SpringBootApplication
+public class SpringBootAdminServerApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(SpringBootAdminServerApplication.class, args);
+ }
+}
diff --git a/spring-boot-admin/spring-boot-admin-server/src/main/java/com/baeldung/springbootadminserver/configs/HazelcastConfig.java b/spring-boot-admin/spring-boot-admin-server/src/main/java/com/baeldung/springbootadminserver/configs/HazelcastConfig.java
new file mode 100644
index 0000000000..b19b7820af
--- /dev/null
+++ b/spring-boot-admin/spring-boot-admin-server/src/main/java/com/baeldung/springbootadminserver/configs/HazelcastConfig.java
@@ -0,0 +1,24 @@
+package com.baeldung.springbootadminserver.configs;
+
+import com.hazelcast.config.Config;
+import com.hazelcast.config.EvictionPolicy;
+import com.hazelcast.config.ListConfig;
+import com.hazelcast.config.MapConfig;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class HazelcastConfig {
+
+ @Bean
+ public Config hazelcast() {
+ return new Config()
+ .setProperty("hazelcast.jmx", "true")
+ .addMapConfig(new MapConfig("spring-boot-admin-application-store")
+ .setBackupCount(1)
+ .setEvictionPolicy(EvictionPolicy.NONE))
+ .addListConfig(new ListConfig("spring-boot-admin-event-store")
+ .setBackupCount(1)
+ .setMaxSize(1000));
+ }
+}
diff --git a/spring-boot-admin/spring-boot-admin-server/src/main/java/com/baeldung/springbootadminserver/configs/NotifierConfiguration.java b/spring-boot-admin/spring-boot-admin-server/src/main/java/com/baeldung/springbootadminserver/configs/NotifierConfiguration.java
new file mode 100644
index 0000000000..10a31464ab
--- /dev/null
+++ b/spring-boot-admin/spring-boot-admin-server/src/main/java/com/baeldung/springbootadminserver/configs/NotifierConfiguration.java
@@ -0,0 +1,42 @@
+package com.baeldung.springbootadminserver.configs;
+
+import de.codecentric.boot.admin.notify.LoggingNotifier;
+import de.codecentric.boot.admin.notify.RemindingNotifier;
+import de.codecentric.boot.admin.notify.filter.FilteringNotifier;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.scheduling.annotation.Scheduled;
+
+import java.util.concurrent.TimeUnit;
+
+@Configuration
+@EnableScheduling
+public class NotifierConfiguration {
+
+ // @Autowired private Notifier notifier;
+
+ @Bean
+ public LoggingNotifier notifier() {
+ return new LoggingNotifier();
+ }
+
+ @Bean
+ public FilteringNotifier filteringNotifier() {
+ return new FilteringNotifier(notifier());
+ }
+
+ @Bean
+ @Primary
+ public RemindingNotifier remindingNotifier() {
+ RemindingNotifier remindingNotifier = new RemindingNotifier(filteringNotifier());
+ remindingNotifier.setReminderPeriod(TimeUnit.MINUTES.toMillis(5));
+ return remindingNotifier;
+ }
+
+ @Scheduled(fixedRate = 60_000L)
+ public void remind() {
+ remindingNotifier().sendReminders();
+ }
+}
diff --git a/spring-boot-admin/spring-boot-admin-server/src/main/java/com/baeldung/springbootadminserver/configs/WebSecurityConfig.java b/spring-boot-admin/spring-boot-admin-server/src/main/java/com/baeldung/springbootadminserver/configs/WebSecurityConfig.java
new file mode 100644
index 0000000000..4a7c8330b7
--- /dev/null
+++ b/spring-boot-admin/spring-boot-admin-server/src/main/java/com/baeldung/springbootadminserver/configs/WebSecurityConfig.java
@@ -0,0 +1,33 @@
+package com.baeldung.springbootadminserver.configs;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+
+@Configuration
+public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ http
+ .formLogin()
+ .loginPage("/login.html")
+ .loginProcessingUrl("/login")
+ .permitAll();
+ http
+ .logout()
+ .logoutUrl("/logout");
+ http
+ .csrf()
+ .disable();
+ http
+ .authorizeRequests()
+ .antMatchers("/login.html", "/**/*.css", "/img/**", "/third-party/**")
+ .permitAll();
+ http
+ .authorizeRequests()
+ .antMatchers("/**")
+ .authenticated();
+ http.httpBasic();
+ }
+}
diff --git a/spring-boot-admin/spring-boot-admin-server/src/main/resources/application.properties b/spring-boot-admin/spring-boot-admin-server/src/main/resources/application.properties
new file mode 100644
index 0000000000..362f6428e8
--- /dev/null
+++ b/spring-boot-admin/spring-boot-admin-server/src/main/resources/application.properties
@@ -0,0 +1,28 @@
+spring.application.name=spring-boot-admin-server
+
+security.user.name=admin
+security.user.password=admin
+
+#configs to connect to self register the admin server as a client
+spring.boot.admin.url=http://localhost:8080
+spring.boot.admin.username=${security.user.name}
+spring.boot.admin.password=${security.user.password}
+
+#configs to give secured server info
+spring.boot.admin.client.metadata.user.name=${security.user.name}
+spring.boot.admin.client.metadata.user.password=${security.user.password}
+
+#mail notifications
+#spring.mail.host=smtp.gmail.com
+#spring.mail.username=test@gmail.com
+#spring.mail.password=password
+#spring.mail.port=587
+#spring.mail.properties.mail.smtp.auth=true
+#spring.mail.properties.mail.smtp.starttls.enable=true
+
+#spring.boot.admin.notify.mail.to=test@gmail.com
+
+#hipchat notifications
+#spring.boot.admin.notify.hipchat.auth-token=
+#spring.boot.admin.notify.hipchat.room-id=
+#spring.boot.admin.notify.hipchat.url=https://youcompany.hipchat.com/v2/
\ No newline at end of file
diff --git a/spring-boot-admin/spring-boot-admin-server/src/main/resources/logback.xml b/spring-boot-admin/spring-boot-admin-server/src/main/resources/logback.xml
new file mode 100644
index 0000000000..ff96acae79
--- /dev/null
+++ b/spring-boot-admin/spring-boot-admin-server/src/main/resources/logback.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+ %date [%thread] %-5level %logger{25} - %msg%n
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/spring-boot-admin/spring-boot-admin-server/src/test/java/com/baeldung/springbootadminserver/HazelcastConfigTest.java b/spring-boot-admin/spring-boot-admin-server/src/test/java/com/baeldung/springbootadminserver/HazelcastConfigTest.java
new file mode 100644
index 0000000000..8ca50a6f75
--- /dev/null
+++ b/spring-boot-admin/spring-boot-admin-server/src/test/java/com/baeldung/springbootadminserver/HazelcastConfigTest.java
@@ -0,0 +1,24 @@
+package com.baeldung.springbootadminserver;
+
+import com.baeldung.springbootadminserver.configs.HazelcastConfig;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.ApplicationContext;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import static org.junit.Assert.assertNotEquals;
+import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.NONE;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = { HazelcastConfig.class }, webEnvironment = NONE)
+public class HazelcastConfigTest {
+
+ @Autowired private ApplicationContext applicationContext;
+
+ @Test
+ public void whenApplicationContextStarts_HazelcastConfigBeanExists() {
+ assertNotEquals(applicationContext.getBean("hazelcast"), null);
+ }
+}
diff --git a/spring-boot-admin/spring-boot-admin-server/src/test/java/com/baeldung/springbootadminserver/NotifierConfigurationTest.java b/spring-boot-admin/spring-boot-admin-server/src/test/java/com/baeldung/springbootadminserver/NotifierConfigurationTest.java
new file mode 100644
index 0000000000..85f6b374a4
--- /dev/null
+++ b/spring-boot-admin/spring-boot-admin-server/src/test/java/com/baeldung/springbootadminserver/NotifierConfigurationTest.java
@@ -0,0 +1,41 @@
+package com.baeldung.springbootadminserver;
+
+import com.baeldung.springbootadminserver.configs.NotifierConfiguration;
+import de.codecentric.boot.admin.notify.Notifier;
+import de.codecentric.boot.admin.notify.RemindingNotifier;
+import de.codecentric.boot.admin.notify.filter.FilteringNotifier;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.ApplicationContext;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import static org.junit.Assert.assertNotEquals;
+import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.NONE;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = { NotifierConfiguration.class }, webEnvironment = NONE)
+public class NotifierConfigurationTest {
+
+ @Autowired private ApplicationContext applicationContext;
+
+ @Test
+ public void whenApplicationContextStart_ThenNotifierBeanExists() {
+ Notifier notifier = (Notifier) applicationContext.getBean("notifier");
+ assertNotEquals(notifier, null);
+ }
+
+ @Test
+ public void whenApplicationContextStart_ThenFilteringNotifierBeanExists() {
+ FilteringNotifier filteringNotifier = (FilteringNotifier) applicationContext.getBean("filteringNotifier");
+ assertNotEquals(filteringNotifier, null);
+ }
+
+ @Test
+ public void whenApplicationContextStart_ThenRemindingNotifierBeanExists() {
+ RemindingNotifier remindingNotifier = (RemindingNotifier) applicationContext.getBean("remindingNotifier");
+ assertNotEquals(remindingNotifier, null);
+ }
+
+}
diff --git a/spring-boot-admin/spring-boot-admin-server/src/test/java/com/baeldung/springbootadminserver/WebSecurityConfigTest.java b/spring-boot-admin/spring-boot-admin-server/src/test/java/com/baeldung/springbootadminserver/WebSecurityConfigTest.java
new file mode 100644
index 0000000000..40611f00f6
--- /dev/null
+++ b/spring-boot-admin/spring-boot-admin-server/src/test/java/com/baeldung/springbootadminserver/WebSecurityConfigTest.java
@@ -0,0 +1,71 @@
+package com.baeldung.springbootadminserver;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.springframework.web.context.WebApplicationContext;
+
+import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin;
+import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+public class WebSecurityConfigTest {
+
+ @Autowired WebApplicationContext wac;
+
+ private MockMvc mockMvc;
+
+ @Before
+ public void setup() {
+ mockMvc = MockMvcBuilders
+ .webAppContextSetup(wac)
+ .build();
+ }
+
+ @Test
+ public void whenApplicationStarts_ThenGetLoginPageWithSuccess() throws Exception {
+ mockMvc
+ .perform(get("/login.html"))
+ .andExpect(status().is2xxSuccessful());
+ }
+
+ @Test
+ public void whenFormLoginAttempted_ThenSuccess() throws Exception {
+ mockMvc.perform(formLogin("/login")
+ .user("admin")
+ .password("admin"));
+ }
+
+ @Test
+ public void whenFormLoginWithSuccess_ThenApiEndpointsAreAccessible() throws Exception {
+ mockMvc.perform(formLogin("/login")
+ .user("admin")
+ .password("admin"));
+
+ mockMvc
+ .perform(get("/api/applications/"))
+ .andExpect(status().is2xxSuccessful());
+
+ }
+
+ @Test
+ public void whenHttpBasicAttempted_ThenSuccess() throws Exception {
+ mockMvc.perform(get("/env").with(httpBasic("admin", "admin")));
+ }
+
+ @Test
+ public void whenInvalidHttpBasicAttempted_ThenUnauthorized() throws Exception {
+ mockMvc
+ .perform(get("/env").with(httpBasic("admin", "invalid")))
+ .andExpect(status().isUnauthorized());
+ }
+
+}