Spring Boot — ntfy-spring-boot-starter¶
Spring Boot auto-configuration that binds the ntfy.* properties, installs an ntfy-auto appender
onto the logging backend your application runs on — Logback or Log4j2 — so your ERROR logs publish
(and, with ntfy.warn-topic, your WARN logs too — see level-routing.md)
with no code, and exposes an injectable NtfyClient bean for manual notifications.
Use this when you run Spring Boot and want alerting configured from application.yml and an
@Autowired NtfyClient to send your own notifications.
Runnable example: examples/spring-boot — a small app using this
starter, whose CI-run integration tests cover the base alert path, sync-vs-async delivery, and the
clean-shutdown digest flush.
Install¶
- Maven Central: io.github.pimak:ntfy-spring-boot-starter
<dependency>
<groupId>io.github.pimak</groupId>
<artifactId>ntfy-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
The starter pulls ntfy-core and ntfy-logback transitively — on the default (Logback) Spring Boot
stack you only declare this one artifact.
On spring-boot-starter-log4j2¶
If you swapped Spring Boot's default backend for Log4j2, add the Log4j2 adapter alongside the starter; everything else is unchanged:
<dependency>
<groupId>io.github.pimak</groupId>
<artifactId>ntfy-log4j2</artifactId>
<version>2.1.0</version>
</dependency>
The starter is backend-agnostic: it installs the ntfy-auto appender onto whichever backend is
actually on the classpath and bound, and the NtfyClient bean and the Micrometer meters below are
available either way. (Earlier releases gated the whole auto-configuration on Logback being present,
so a Log4j2 application got no appender, no NtfyClient bean, and no metrics at all.)
ntfy-logback still arrives transitively, unchanged, and is inert on a Log4j2 stack: it declares
Logback itself at provided scope, so it never pulls logback-classic onto your classpath and its
appender is simply never installed.
Base configuration¶
# application.yml
ntfy:
url: https://ntfy.example.com
topic: my-app-alerts
token: tk_xxxxxxxxxxxxxxxxxxxxxxxxxxxx # or username/password; omit for a public topic
That is all you need. As soon as url and topic are set, error logs auto-publish (an ntfy-auto
appender is installed idempotently on the bound backend) — no logback.xml / log4j2.xml edit
required.
Delivery is asynchronous by default (since 2.0): alerts are offloaded to a bounded queue drained by
a daemon worker (tune it with ntfy.async-queue-capacity, default 1024), so a slow or unreachable
ntfy server never blocks your application threads. Set ntfy.async: false for pre-2.0 synchronous,
inline delivery. See alert-behavior.md.
MDC context in alert bodies¶
To make an alert say for whom and in which request something broke, list the MDC keys you want rendered into the body:
Each named key that carries a non-blank value appears as its own key: value line just after the
Logger: line, in the order listed. Relaxed binding applies as it does to every other key, so
ntfy.includeMdcKeys and NTFY_INCLUDE_MDC_KEYS bind equally. The starter's appender is a Logback
appender, so values come from ILoggingEvent.getMDCPropertyMap() — captured at event construction
and therefore correct even behind an AsyncAppender.
This is an explicit allow-list with no wildcard and no "include everything" mode: nothing from the MDC is published unless you name its key. Unset (the default) means alert bodies are exactly what they were before. See mdc-context.md for the per-value guards and the rationale.
Startup self-test¶
Alerting only speaks when something breaks, so a revoked token or a wrong topic stays invisible until the first real error alert fails to deliver. The opt-in self-test makes one round-trip at boot and reports a clear diagnostic instead:
probe is read-only and publishes nothing; publish sends one low-priority test notification
through the production path and is the only mode that proves alerts are genuinely deliverable
(ntfy grants read and write separately). It runs in the background and never delays startup. Add
ntfy.startup-ping-fail-fast: true to turn a failure into a failed
startup in CI or staging. Full details, including the failure diagnostics, in
configuration.md.
startup-ping covers the primary topic only. The optional WARN route has its own independent flag —
ntfy grants permissions per topic, so a healthy ERROR route proves nothing about the WARN one:
When one route fails and another passed, the diagnosis is published as a notification on the passing
route (startup-ping-notify-failures, on by default), so a broken route reaches you rather than only
a status log.
Manual notifications¶
Inject the client to send your own notifications:
@Autowired NtfyClient ntfy; // or constructor injection
ntfy.notify("Deploy finished", "v1.2.3 is live");
NtfyClient.notify(...) never throws — every outcome is returned as a PublishResult.
Metrics¶
When micrometer-core is on the classpath (it is in any Spring Boot Actuator app) and a
MeterRegistry bean exists, the starter automatically exports three monotonic counters —
ntfy.pipeline.published, ntfy.pipeline.suppressed, ntfy.pipeline.failed — so you can alert on a
spike in publish failures. The binding is classpath-conditional and adds no dependency when
Micrometer is absent; it does not depend on which logging backend you run. See
Observability for the full semantics.
The example, in full¶
The runnable examples/spring-boot app boots on exactly this configuration — CI compiles it and its integration tests run against it, so nothing shown here
can drift from what is actually tested.
# Example ntfy configuration for a Spring Boot application. These two keys are all the starter
# needs to publish every ERROR log as a push notification and to expose the injectable NtfyClient.
# The module's integration tests boot this application with the url/topic overridden (via
# command-line properties) to point at a loopback ntfy stand-in.
ntfy:
url: https://ntfy.example.com
topic: my-app-alerts
# For an auth-protected topic:
# token: tk_xxxxxxxxxxxxxxxxxxxxxxxxxxxx
# Built-in async delivery (the default since 2.0): each error alert is handed to a bounded queue
# drained by a daemon worker, so a slow or unreachable ntfy server never blocks the logging
# thread. Set async: false for pre-2.0 synchronous, inline delivery — the guarantee a short-lived
# process may prefer, since the publish completes before the logging call returns.
# async: false
# async-queue-capacity: 1024
# Storm suppression (defaults shown): at most max-alerts-per-window individual alerts per
# suppression-window; the overflow is reported as one aggregated digest — also flushed when the
# application context closes, so suppressed errors are never lost.
# max-alerts-per-window: 3
# suppression-window: 3m
Going further¶
The base config above covers the common case. Everything else is shared across all adapters and lives in the cross-cutting reference pages:
- Configuration reference — every
ntfy.*key (types, defaults) bound with Spring's relaxed binding, plus duration syntax. - Authentication —
tokenvsusername/passwordand the token-wins rule. - Alert behavior — immediate alerts, storm suppression, and digests.
- MDC context — the
include-mdc-keysallow-list, its guards, and why there is no wildcard. - Level routing — the
warn-topicopt-in: which levels alert and where. - Filtering —
excluded-loggers, theNO_ALERTmarker, and self-exclusion. - Troubleshooting — the diagnostics the engine emits and where Spring
surfaces them (Logback's
StatusManager, or Log4j2'sStatusLoggeron a Log4j2 stack). - Compatibility — tested Spring Boot / JDK / Logback / Log4j2 versions.
- Plain Log4j2 — the adapter the starter uses on a
spring-boot-starter-log4j2classpath, and its standalone entry points.