spring-boot-admin-docs/src/site/docs/09-samples/10-sample-servlet.md
The Servlet sample demonstrates a complete Spring Boot Admin Server deployment using traditional servlet-based Spring MVC. This is the most feature-rich sample, showcasing security, custom UI extensions, notifications, and self-monitoring.
Location: spring-boot-admin-samples/spring-boot-admin-sample-servlet/
Features:
cd spring-boot-admin-samples/spring-boot-admin-sample-servlet
mvn spring-boot:run
Access the application at: http://localhost:8080
mvn spring-boot:run -Dspring-boot.run.profiles=secure
Login Credentials:
userpasswordSERVER_PORT=9090 mvn spring-boot:run
Key dependencies from pom.xml:
<dependencies>
<!-- Admin Server -->
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-server</artifactId>
</dependency>
<!-- Admin Client (for self-monitoring) -->
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
</dependency>
<!-- Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- Web (Servlet) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc</artifactId>
</dependency>
<!-- Notifications -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<!-- Session Management -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-jdbc</artifactId>
</dependency>
<!-- Custom UI Extensions -->
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-sample-custom-ui</artifactId>
</dependency>
</dependencies>
@SpringBootApplication
@EnableAdminServer
@EnableCaching
public class SpringBootAdminServletApplication {
static void main(String[] args) {
SpringApplication app = new SpringApplication(
SpringBootAdminServletApplication.class
);
app.setApplicationStartup(new BufferingApplicationStartup(1500));
app.run(args);
}
@Bean
public CustomNotifier customNotifier(InstanceRepository repository) {
return new CustomNotifier(repository);
}
@Bean
public HttpHeadersProvider customHttpHeadersProvider() {
return (instance) -> {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("X-CUSTOM", "My Custom Value");
return httpHeaders;
};
}
@Bean
public InstanceExchangeFilterFunction auditLog() {
return (instance, request, next) -> next.exchange(request)
.doOnSubscribe((s) -> {
if (HttpMethod.DELETE.equals(request.method())
|| HttpMethod.POST.equals(request.method())) {
log.info("{} for {} on {}",
request.method(), instance.getId(), request.url());
}
});
}
}
Key Points:
@EnableAdminServer activates Admin Server functionalityspring:
application:
name: spring-boot-admin-sample-servlet
boot:
admin:
client:
url: http://localhost:8080 # Self-registration
instance:
service-host-type: IP
metadata:
tags:
environment: test
management:
endpoints:
web:
exposure:
include: "*"
endpoint:
health:
show-details: ALWAYS
shutdown:
enabled: true
restart:
enabled: true
logging:
file:
name: "target/boot-admin-sample-servlet.log"
level:
de.codecentric: info
The sample includes multiple static instances with creative names:
spring:
cloud:
discovery:
client:
simple:
instances:
"Captain Debugbeard":
- uri: http://localhost:8080
metadata:
management.context-path: /actuator
group: Pirates of the Caribbean Bean
"Stack Overflow Sorcerer":
- uri: http://localhost:8080
metadata:
management.context-path: /actuator
group: Wizarding World
@Profile("secure")
@Configuration
public class SecuritySecureConfig {
private final AdminServerProperties adminServer;
@Bean
protected SecurityFilterChain filterChain(HttpSecurity http)
throws Exception {
SavedRequestAwareAuthenticationSuccessHandler successHandler =
new SavedRequestAwareAuthenticationSuccessHandler();
successHandler.setTargetUrlParameter("redirectTo");
successHandler.setDefaultTargetUrl(adminServer.path("/"));
http.authorizeHttpRequests((authorizeRequests) -> authorizeRequests
.requestMatchers(adminServer.path("/assets/**"))
.permitAll() // Allow static resources
.requestMatchers(adminServer.path("/actuator/info"))
.permitAll()
.requestMatchers(adminServer.path("/actuator/health"))
.permitAll()
.requestMatchers(adminServer.path("/login"))
.permitAll()
.anyRequest()
.authenticated())
.formLogin((formLogin) -> formLogin
.loginPage(adminServer.path("/login"))
.successHandler(successHandler))
.logout((logout) -> logout
.logoutUrl(adminServer.path("/logout")))
.httpBasic(Customizer.withDefaults());
// CSRF Configuration
http.csrf((csrf) -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.ignoringRequestMatchers(
adminServer.path("/instances"), // Instance registration
adminServer.path("/instances/*"), // Instance deregistration
adminServer.path("/actuator/**") // Actuator endpoints
));
http.rememberMe((rememberMe) -> rememberMe
.key(UUID.randomUUID().toString())
.tokenValiditySeconds(1209600)); // 14 days
return http.build();
}
@Bean
public InMemoryUserDetailsManager userDetailsService(
PasswordEncoder passwordEncoder) {
UserDetails user = User.withUsername("user")
.password(passwordEncoder.encode("password"))
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Security Features:
The sample includes a custom notifier implementation:
public class CustomNotifier extends AbstractEventNotifier {
private static final Logger LOGGER =
LoggerFactory.getLogger(CustomNotifier.class);
public CustomNotifier(InstanceRepository repository) {
super(repository);
}
@Override
protected Mono<Void> doNotify(InstanceEvent event, Instance instance) {
return Mono.fromRunnable(() -> {
if (event instanceof InstanceStatusChangedEvent statusChangedEvent) {
LOGGER.info("Instance {} ({}) is {}",
instance.getRegistration().getName(),
event.getInstance(),
statusChangedEvent.getStatusInfo().getStatus());
} else {
LOGGER.info("Instance {} ({}) {}",
instance.getRegistration().getName(),
event.getInstance(),
event.getType());
}
});
}
}
@Configuration
public class NotifierConfig {
@Bean
public FilteringNotifier filteringNotifier() {
CompositeNotifier delegate = new CompositeNotifier(
otherNotifiers.getIfAvailable(Collections::emptyList)
);
return new FilteringNotifier(delegate, repository);
}
@Primary
@Bean(initMethod = "start", destroyMethod = "stop")
public RemindingNotifier remindingNotifier() {
RemindingNotifier notifier = new RemindingNotifier(
filteringNotifier(), repository
);
notifier.setReminderPeriod(Duration.ofMinutes(10));
notifier.setCheckReminderInverval(Duration.ofSeconds(10));
return notifier;
}
}
Notification Features:
The sample demonstrates various external view configurations:
spring:
boot:
admin:
ui:
external-views:
# Simple link
- label: "š"
url: "https://codecentric.de"
order: 2000
# Dropdown with links
- label: Resources
children:
- label: "š Docs"
url: https://codecentric.github.io/spring-boot-admin/
- label: "š¦ Maven"
url: https://search.maven.org/...
- label: "š GitHub"
url: https://github.com/codecentric/spring-boot-admin
# Iframe view
- label: "š
Is it christmas"
url: https://isitchristmas.com
iframe: true
spring:
boot:
admin:
ui:
view-settings:
- name: "journal"
enabled: false
The sample uses JDBC-based session persistence:
@Bean
public EmbeddedDatabase dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("org/springframework/session/jdbc/schema-hsqldb.sql")
.build();
}
Benefits:
http://localhost:8080The application monitors itself via the Admin Client. You should see:
spring-boot-admin-sample-servletMonitor the logs for custom notification events:
INFO - Instance spring-boot-admin-sample-servlet (...) is UP
INFO - Instance spring-boot-admin-sample-servlet (...) ENDPOINTS_DETECTED
All requests to instances include the custom header X-CUSTOM: My Custom Value.
Click the external view links in the navigation:
mvn clean package
Produces: target/spring-boot-admin-sample-servlet.jar
java -jar target/spring-boot-admin-sample-servlet.jar
java -jar target/spring-boot-admin-sample-servlet.jar \
--spring.profiles.active=secure
When deploying, consider:
Example deployment configuration:
spring:
datasource:
url: jdbc:postgresql://localhost:5432/admin
username: admin
password: ${DB_PASSWORD}
mail:
host: smtp.company.com
port: 587
username: ${SMTP_USER}
password: ${SMTP_PASSWORD}
server:
port: 8443
ssl:
enabled: true
key-store: classpath:keystore.p12
key-store-password: ${KEYSTORE_PASSWORD}
Create a custom endpoint (example included):
@Component
@Endpoint(id = "custom")
public class CustomEndpoint {
@ReadOperation
public Map<String, Object> customEndpoint() {
return Map.of(
"message", "Hello from custom endpoint",
"timestamp", Instant.now()
);
}
}
Replace log-based notifier with database persistence:
public class DatabaseNotifier extends AbstractEventNotifier {
private final EventRepository eventRepository;
@Override
protected Mono<Void> doNotify(InstanceEvent event, Instance instance) {
return Mono.fromRunnable(() -> {
eventRepository.save(new EventEntity(event, instance));
});
}
}
Enhance self-registration with custom metadata:
spring:
boot:
admin:
client:
instance:
metadata:
version: ${project.version}
region: us-east-1
team: platform
tags:
environment: production
cost-center: engineering
# Change port
SERVER_PORT=9090 mvn spring-boot:run
If you cannot access the UI:
secure profile is activeuser / passwordlogging:
level:
org.springframework.mail: DEBUG
This sample demonstrates:
ā Complete Deployment Setup
ā Self-Monitoring Pattern
ā Extensibility
ā Best Practices