Creating Custom Notifiers
Spring Boot Admin makes it easy to create custom notifiers to integrate with your preferred notification channels. You
can extend the built-in notifier base classes or implement the Notifier interface directly.
Overview
Notifiers are Spring beans that implement the Notifier interface and react to instance events such as status changes,
registration, or deregistration.
Using AbstractEventNotifier
The recommended approach is to extend AbstractEventNotifier, which provides built-in support for:
- Filtering events
- Enabling/disabling notifications
- Accessing instance details
- Error handling
Basic Example
import de.codecentric.boot.admin.server.domain.entities.Instance;
import de.codecentric.boot.admin.server.domain.entities.InstanceRepository;
import de.codecentric.boot.admin.server.domain.events.InstanceEvent;
import de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent;
import de.codecentric.boot.admin.server.notify.AbstractEventNotifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Mono;
public class CustomNotifier extends AbstractEventNotifier {
private static final Logger log = 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 statusEvent) {
log.info("Instance {} ({}) is {}",
instance.getRegistration().getName(),
event.getInstance(),
statusEvent.getStatusInfo().getStatus());
} else {
log.info("Instance {} ({}) {}",
instance.getRegistration().getName(),
event.getInstance(),
event.getType());
}
});
}
}
Registering the Notifier
Register your custom notifier as a Spring bean:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class NotifierConfiguration {
@Bean
public CustomNotifier customNotifier(InstanceRepository repository) {
return new CustomNotifier(repository);
}
}
Advanced Custom Notifier
Here's a more advanced example that sends notifications to an external API:
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
public class WebhookNotifier extends AbstractEventNotifier {
private static final Logger log = LoggerFactory.getLogger(WebhookNotifier.class);
private final WebClient webClient;
private final String webhookUrl;
public WebhookNotifier(InstanceRepository repository,
WebClient.Builder webClientBuilder,
String webhookUrl) {
super(repository);
this.webhookUrl = webhookUrl;
this.webClient = webClientBuilder.build();
}
@Override
protected Mono<Void> doNotify(InstanceEvent event, Instance instance) {
return Mono.fromSupplier(() -> createNotificationPayload(event, instance))
.flatMap(this::sendWebhookNotification)
.doOnError(ex -> log.error("Failed to send webhook notification", ex))
.then();
}
private NotificationPayload createNotificationPayload(InstanceEvent event,
Instance instance) {
return NotificationPayload.builder()
.instanceId(instance.getId().getValue())
.instanceName(instance.getRegistration().getName())
.eventType(event.getType())
.status(instance.getStatusInfo().getStatus())
.timestamp(event.getTimestamp())
.serviceUrl(instance.getRegistration().getServiceUrl())
.build();
}
private Mono<Void> sendWebhookNotification(NotificationPayload payload) {
return webClient.post()
.uri(webhookUrl)
.bodyValue(payload)
.retrieve()
.bodyToMono(Void.class)
.doOnSuccess(v -> log.info("Webhook notification sent successfully"))
.onErrorResume(ex -> {
log.error("Webhook call failed: {}", ex.getMessage());
return Mono.empty();
});
}
@lombok.Data
@lombok.Builder
private static class NotificationPayload {
private String instanceId;
private String instanceName;
private String eventType;
private String status;
private long timestamp;
private String serviceUrl;
}
}
Configuration
@Configuration
public class WebhookNotifierConfiguration {
@Bean
public WebhookNotifier webhookNotifier(InstanceRepository repository,
WebClient.Builder webClientBuilder,
@Value("${webhook.url}") String webhookUrl) {
return new WebhookNotifier(repository, webClientBuilder, webhookUrl);
}
}
webhook:
url: https://your-webhook-endpoint.com/notifications
Filtering Events
You can override shouldNotify to filter which events trigger notifications:
public class FilteredNotifier extends AbstractEventNotifier {
public FilteredNotifier(InstanceRepository repository) {
super(repository);
}
@Override
protected boolean shouldNotify(InstanceEvent event, Instance instance) {
// Only notify for production instances
String environment = instance.getRegistration()
.getMetadata()
.get("environment");
return "production".equals(environment);
}
@Override
protected Mono<Void> doNotify(InstanceEvent event, Instance instance) {
// Send notification
return Mono.fromRunnable(() -> {
log.info("Production instance event: {}", event.getType());
});
}
}
Using AbstractStatusChangeNotifier
If you only care about status changes (UP/DOWN/OFFLINE), extend AbstractStatusChangeNotifier:
import de.codecentric.boot.admin.server.domain.entities.Instance;
import de.codecentric.boot.admin.server.domain.entities.InstanceRepository;
import de.codecentric.boot.admin.server.domain.values.StatusInfo;
import de.codecentric.boot.admin.server.notify.AbstractStatusChangeNotifier;
import reactor.core.publisher.Mono;
public class StatusChangeNotifier extends AbstractStatusChangeNotifier {
public StatusChangeNotifier(InstanceRepository repository) {
super(repository);
}
@Override
protected Mono<Void> doNotify(InstanceEvent event, Instance instance) {
StatusInfo statusInfo = instance.getStatusInfo();
String status = statusInfo.getStatus();
return Mono.fromRunnable(() -> {
if ("DOWN".equals(status)) {
// Send critical alert
log.error("CRITICAL: Instance {} is DOWN!",
instance.getRegistration().getName());
} else if ("UP".equals(status)) {
// Send recovery notification
log.info("Instance {} is back UP",
instance.getRegistration().getName());
}
});
}
}
Implementing Notifier Interface Directly
For full control, implement the Notifier interface:
import de.codecentric.boot.admin.server.notify.Notifier;
import reactor.core.publisher.Mono;
public class DirectNotifier implements Notifier {
private final InstanceRepository repository;
private boolean enabled = true;
public DirectNotifier(InstanceRepository repository) {
this.repository = repository;
}
@Override
public Mono<Void> notify(InstanceEvent event) {
if (!enabled) {
return Mono.empty();
}
return repository.find(event.getInstance())
.flatMap(instance -> processNotification(event, instance))
.then();
}
private Mono<Void> processNotification(InstanceEvent event, Instance instance) {
// Custom notification logic
return Mono.fromRunnable(() -> {
// Send notification
});
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}
Configuration Properties
Make your notifier configurable through application properties:
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "spring.boot.admin.notify.custom")
public class CustomNotifierProperties {
private final boolean enabled = true;
private String apiUrl;
private String apiKey;
private final int timeout = 5000;
// Getters and setters
}
@Configuration
@EnableConfigurationProperties(CustomNotifierProperties.class)
public class CustomNotifierConfiguration {
@Bean
@ConditionalOnProperty(prefix = "spring.boot.admin.notify.custom",
name = "enabled",
havingValue = "true",
matchIfMissing = true)
public CustomNotifier customNotifier(InstanceRepository repository,
CustomNotifierProperties properties) {
CustomNotifier notifier = new CustomNotifier(repository);
notifier.setApiUrl(properties.getApiUrl());
notifier.setApiKey(properties.getApiKey());
notifier.setTimeout(properties.getTimeout());
return notifier;
}
}
spring:
boot:
admin:
notify:
custom:
enabled: true
api-url: https://api.example.com/notifications
api-key: ${NOTIFICATION_API_KEY}
timeout: 10000
Combining with FilteringNotifier
Use FilteringNotifier to allow runtime control:
@Configuration
public class NotifierConfig {
@Bean
public FilteringNotifier filteringNotifier(InstanceRepository repository,
ObjectProvider<List<Notifier>> otherNotifiers) {
CompositeNotifier delegate = new CompositeNotifier(
otherNotifiers.getIfAvailable(Collections::emptyList));
return new FilteringNotifier(delegate, repository);
}
@Primary
@Bean(initMethod = "start", destroyMethod = "stop")
public RemindingNotifier remindingNotifier(FilteringNotifier filteringNotifier,
InstanceRepository repository) {
RemindingNotifier notifier = new RemindingNotifier(
filteringNotifier, repository);
notifier.setReminderPeriod(Duration.ofMinutes(10));
notifier.setCheckReminderInverval(Duration.ofSeconds(10));
return notifier;
}
@Bean
public CustomNotifier customNotifier(InstanceRepository repository) {
return new CustomNotifier(repository);
}
}
Testing Custom Notifiers
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import reactor.test.StepVerifier;
public class CustomNotifierTest {
@Test
public void testNotification() {
InstanceRepository repository = Mockito.mock(InstanceRepository.class);
CustomNotifier notifier = new CustomNotifier(repository);
Instance instance = Instance.create(InstanceId.of("test-instance"))
.register(Registration.create("test-app", "http://localhost:8080")
.build());
InstanceEvent event = new InstanceStatusChangedEvent(
instance.getId(),
instance.getVersion(),
StatusInfo.ofUp()
);
Mockito.when(repository.find(instance.getId()))
.thenReturn(Mono.just(instance));
StepVerifier.create(notifier.notify(event))
.verifyComplete();
}
}
Best Practices
- Extend AbstractEventNotifier for most use cases - it provides essential features
- Handle errors gracefully - don't let notification failures affect the server
- Use reactive programming - return
Mono<Void>for async operations - Make it configurable - use
@ConfigurationPropertiesfor flexibility - Filter appropriately - override
shouldNotifyto reduce noise - Log failures - always log when notifications fail for debugging
- Use WebClient for HTTP calls - it's reactive and efficient
- Consider rate limiting - prevent notification storms
- Test thoroughly - ensure your notifier handles all event types
- Document configuration - provide clear examples for users
See Also
- Notifications Overview - Learn about the notification system
- Filtering Notifications - Control which notifications are sent
- Notification Reminders - Set up reminder notifications
- Events - Understand instance events