pulse-spring-boot-starter
Used in:
components
- OverviewOverview
- VersionsVersions
- DependentsDependents
- DependenciesDependencies
<dependency>
<groupId>io.github.arun0009</groupId>
<artifactId>pulse-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency><?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>io.github.arun0009</groupId>
<artifactId>pulse-spring-boot-starter</artifactId>
<version>2.0.0</version>
<packaging>jar</packaging>
<name>Pulse — Production correctness for Spring Boot</name>
<description>Opinionated production-correctness layer for Spring Boot. Cardinality firewall, timeout-budget propagation, wide-event API, structured logging, and trace-correlated MDC — pre-installed.</description>
<url>https://github.com/arun0009/pulse</url>
<organization>
<name>Pulse contributors</name>
<url>https://github.com/arun0009/pulse</url>
</organization>
<licenses>
<license>
<name>MIT License</name>
<url>https://opensource.org/licenses/MIT</url>
<distribution>repo</distribution>
</license>
</licenses>
<developers>
<developer>
<id>arun0009</id>
<name>Arun Gopalpuri</name>
<email>arun0009@gmail.com</email>
<url>https://github.com/arun0009</url>
</developer>
</developers>
<scm>
<connection>scm:git:https://github.com/arun0009/pulse.git</connection>
<developerConnection>scm:git:ssh://git@github.com/arun0009/pulse.git</developerConnection>
<tag>HEAD</tag>
<url>https://github.com/arun0009/pulse</url>
</scm>
<issueManagement>
<system>GitHub Issues</system>
<url>https://github.com/arun0009/pulse/issues</url>
</issueManagement>
<ciManagement>
<system>GitHub Actions</system>
<url>https://github.com/arun0009/pulse/actions</url>
</ciManagement>
<properties>
<!-- Static analysis (Google's Error Prone + Uber's NullAway, configured
per the JSpecify-mode recipe). NullAway only checks @NullMarked
packages so it's incremental-friendly. -->
<error-prone-core.version>2.49.0</error-prone-core.version>
<java.version>21</java.version>
<jreleaser-maven-plugin.version>1.23.0</jreleaser-maven-plugin.version>
<jspecify.version>1.0.0</jspecify.version>
<!-- Build / publish toolchain — versions aligned with arun0009/idempotent for
consistency across the io.github.arun0009 namespace. -->
<maven-jar-plugin.version>3.5.0</maven-jar-plugin.version>
<maven-javadoc-plugin.version>3.12.0</maven-javadoc-plugin.version>
<maven-source-plugin.version>3.4.0</maven-source-plugin.version>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<nullaway.version>0.13.3</nullaway.version>
<opentelemetry.semconv.version>1.40.0</opentelemetry.semconv.version>
<!-- Reproducible builds: byte-identical artifacts across rebuilds.
Override per-release via -Dproject.build.outputTimestamp=… or set to a
commit SHA / commit time. See https://maven.apache.org/guides/mini/guide-reproducible-builds.html -->
<project.build.outputTimestamp>2026-04-20T16:54:26Z</project.build.outputTimestamp>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<resilience4j.version>2.4.0</resilience4j.version>
<spotless.version>3.4.0</spotless.version>
<spring.boot.version>4.0.5</spring.boot.version>
<versions-maven-plugin.version>2.21.0</versions-maven-plugin.version>
<wiremock.version>3.13.2</wiremock.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-bom</artifactId>
<version>2.0.4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- ========================================================================
Spring Boot — provided (consuming services bring their own).
provided scope is available at compile, test, and runtime in
the consumer; it just isn't transitively forced.
======================================================================== -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aspectj</artifactId>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-micrometer-metrics</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-restclient</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-webclient</artifactId>
<optional>true</optional>
</dependency>
<!-- Log4j2 — transitive so that Pulse's JSON layout, PII masking, and
trace/span MDC enrichment work out of the box. Consumers who prefer
Logback (Spring Boot's default) should exclude
`spring-boot-starter-log4j2` here and add `spring-boot-starter-logging`;
Pulse ships an equivalent `logback-spring.xml` + custom encoder that
Spring Boot's LoggingSystem auto-loads in that case. -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-layout-template-json</artifactId>
</dependency>
<!-- Logback — optional, compile-only. Lets PulseLogbackEncoder reference
ch.qos.logback types without forcing Logback onto consumers who use
the default Log4j2 path. Consumers who exclude spring-boot-starter-log4j2
will already have logback-classic transitively via spring-boot-starter-logging. -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<optional>true</optional>
</dependency>
<!-- Hibernate ORM + Spring Boot JPA — optional, compile-only. Lets Pulse compile
against org.hibernate.resource.jdbc.spi.StatementInspector and
org.springframework.boot.jpa.autoconfigure.HibernatePropertiesCustomizer for the
database-observability subsystem (N+1 detection, statements-per-request histogram,
slow-query threshold). Consumers who use Spring Data JPA already have both
transitively; for non-JPA apps the whole subsystem is short-circuited via
@ConditionalOnClass. -->
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-core</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-hibernate</artifactId>
<optional>true</optional>
</dependency>
<!-- Resilience4j — optional, compile-only. Lets Pulse compile against
CircuitBreakerRegistry / RetryRegistry / RateLimiterRegistry / BulkheadRegistry /
TimeLimiterRegistry so it can attach event consumers when these registries are
wired by the consumer (typically through resilience4j-spring-boot3 or hand-rolled).
Apps that don't use Resilience4j short-circuit the whole subsystem via
@ConditionalOnClass at zero startup cost. -->
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-circuitbreaker</artifactId>
<version>${resilience4j.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-retry</artifactId>
<version>${resilience4j.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-bulkhead</artifactId>
<version>${resilience4j.version}</version>
<optional>true</optional>
</dependency>
<!--
jakarta.servlet types are reached transitively via spring-boot-starter-web →
tomcat-embed-core (provided scope). Declaring jakarta.servlet-api directly created
a JPMS split-package conflict during Javadoc generation — both jars export the
same packages, so the release Javadoc jar would not build. tomcat-embed-core is
sufficient for compilation and any consumer using Pulse with a non-Tomcat servlet
container will still bring their own jakarta.servlet implementation.
-->
<!-- ========================================================================
OpenTelemetry — Spring Boot 4 native starter + OTel SDK + OTLP.
Transitive (compile scope) so consumers get them automatically; this
is what makes Pulse a "starter" rather than a library that requires
ten extra dependency declarations.
======================================================================== -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-opentelemetry</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-api</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-sdk-trace</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-sdk-common</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-otlp</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry.semconv</groupId>
<artifactId>opentelemetry-semconv</artifactId>
<version>${opentelemetry.semconv.version}</version>
</dependency>
<!-- ========================================================================
Micrometer — metrics facade + tracing bridge + OTLP push
======================================================================== -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-core</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-otel</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-otlp</artifactId>
</dependency>
<!-- ========================================================================
SLF4J — compile scope, Pulse uses it directly
======================================================================== -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<!-- ========================================================================
Bean Validation — JSR-380 constraints on @ConfigurationProperties so a
misconfigured pulse.* value (negative timeout, ratio outside [0..1],
blank header name, etc.) fails at startup with a Pulse-friendly message
instead of failing weirdly at runtime. spring-boot-starter-validation
pulls in jakarta.validation-api + Hibernate Validator + Jakarta EL.
Provided scope: most consumers already have it transitively
(spring-data, spring-web, etc.); the few that don't get it forced once
they declare the Pulse starter, with no Pulse-specific opt-in.
======================================================================== -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- ========================================================================
Spring Boot configuration metadata generation
======================================================================== -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!-- ========================================================================
Optional integrations — instrumented when the user puts them on classpath
======================================================================== -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<optional>true</optional>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- OkHttp — needed at compile time so OkHttpPropagationConfiguration can
reference okhttp3 types. Optional so consumers don't get it transitively;
the configuration is gated on @ConditionalOnClass(OkHttpClient.class) and
only activates when the application puts okhttp on its own classpath. -->
<!-- okhttp 5.x splits its artifact into a multiplatform stub (okhttp) and the JVM
implementation (okhttp-jvm) that actually contains okhttp3.Request, .Interceptor, etc.
We pin okhttp-jvm here because that's what OTel's OTLP HTTP sender pulls in transitively
at runtime, and our optional OkHttp interceptor needs the same types at compile time.
Marked optional so consumers who don't already use OkHttp aren't forced into it. -->
<!-- Apache HttpClient 5 — optional, compile-only. Lets Pulse compile against
org.apache.hc.client5.http.classic.HttpClient and
org.apache.hc.client5.http.impl.classic.HttpClientBuilder so
ApacheHttpClient5PropagationConfiguration can wrap the user's HttpClientBuilder
beans with a Pulse HttpRequestInterceptor. Consumers who already use Spring's
RestTemplate get propagation via the existing RestTemplate customizer — this
optional dep only kicks in when the application builds an HttpClient directly
(e.g. for non-Spring HTTP calls) and puts the builder in the Spring context. -->
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>5.4.2</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp-jvm</artifactId>
<version>5.3.2</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
<optional>true</optional>
</dependency>
<!-- OpenFeature — instrumented when present so flag evaluations land on MDC
and as span events. Optional: pulled in only by apps that already use it. -->
<dependency>
<groupId>dev.openfeature</groupId>
<artifactId>sdk</artifactId>
<version>1.20.2</version>
<optional>true</optional>
</dependency>
<!-- Caffeine — instrumented when present so cache hit rate / load time / evictions land
on Micrometer and so cache misses can stamp span events. Optional: pulled in only by
apps that already use it for @Cacheable storage. -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>3.2.3</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
<optional>true</optional>
</dependency>
<!-- ========================================================================
Test slice (@PulseTest, PulseTestHarness) — shipped in src/main so
consumers can pull it in via the starter and use it from their own
test sources. All test-time helpers are marked optional so they're
not transitively forced on consumers' production classpaths; users
of @PulseTest already have JUnit + AssertJ + spring-boot-test in
their test scope via spring-boot-starter-test.
======================================================================== -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test-autoconfigure</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-sdk-testing</artifactId>
<optional>true</optional>
</dependency>
<!-- JSpecify nullness annotations. Provided + optional: consumers don't
need it transitively, but it's required for our own @NullMarked
package-info files to compile. -->
<dependency>
<groupId>org.jspecify</groupId>
<artifactId>jspecify</artifactId>
<version>${jspecify.version}</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<!-- ========================================================================
Test — Pulse's own test slice + in-memory exporter + Testcontainers + WireMock
======================================================================== -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-opentelemetry-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-testcontainers</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<!-- WireMock — the standalone fat-jar ships a bundled Jetty, so it doesn't depend
on the Jetty version Spring Boot's BOM pins (Jetty 12, while WireMock 3.x still
expects Jetty 11). Shipping it as a fat jar means WireMock also ships a relocated
SLF4J under `wiremock.org.slf4j`, which would ClassCastException if
`-Dslf4j.provider=...` is set. We solve that in the failsafe plugin config below
by excluding logback-classic from the IT classpath (so SLF4J only sees one
provider, no pinning needed, no shaded-SLF4J conflict). -->
<dependency>
<groupId>org.wiremock</groupId>
<artifactId>wiremock-standalone</artifactId>
<version>${wiremock.version}</version>
<scope>test</scope>
</dependency>
<!-- JMH — present at test compile so benchmark sources always build,
but only executed under the `bench` profile via exec:java. -->
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>1.37</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>1.37</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>${maven-jar-plugin.version}</version>
<configuration>
<archive>
<manifest>
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
</manifest>
<manifestEntries>
<Automatic-Module-Name>io.github.arun0009.pulse</Automatic-Module-Name>
</manifestEntries>
</archive>
</configuration>
</plugin>
<!-- versions-maven-plugin — pinned with a stable-only ruleset so
`versions:display-*-updates` and `versions:use-latest-releases`
skip -M*, -RC*, -beta*, -alpha*, -SNAPSHOT candidates.
Day-to-day bumps come from Dependabot (.github/dependabot.yml);
this is the manual ground-truth check (`make versions`) before
cutting a release branch. -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>versions-maven-plugin</artifactId>
<version>${versions-maven-plugin.version}</version>
<configuration>
<generateBackupPoms>false</generateBackupPoms>
<ruleSet>
<ignoreVersions>
<ignoreVersion>
<type>regex</type>
<version>(?i).*-M\d+</version>
</ignoreVersion>
<ignoreVersion>
<type>regex</type>
<version>(?i).*-RC\d*</version>
</ignoreVersion>
<ignoreVersion>
<type>regex</type>
<version>(?i).*-beta.*</version>
</ignoreVersion>
<ignoreVersion>
<type>regex</type>
<version>(?i).*-alpha.*</version>
</ignoreVersion>
<ignoreVersion>
<type>regex</type>
<version>(?i).*-SNAPSHOT</version>
</ignoreVersion>
</ignoreVersions>
</ruleSet>
</configuration>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<!-- maven-dependency-plugin:properties — resolves each declared dependency's
jar path into a property named `<groupId>:<artifactId>:<type>` (e.g.
`org.mockito:mockito-core:jar`). Used below to pin mockito-core as a
proper -javaagent so Mockito 5 doesn't self-attach at runtime on JDK 21+,
which triggered three separate JVM warnings per test fork. -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.7.0</version>
<executions>
<execution>
<id>resolve-agent-jars</id>
<goals>
<goal>properties</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<release>${java.version}</release>
<parameters>true</parameters>
<encoding>UTF-8</encoding>
<fork>true</fork>
<annotationProcessorPaths>
<!-- Spring Boot's configuration processor — generates
META-INF/spring-configuration-metadata.json from every
@ConfigurationProperties record so IntelliJ / VS Code can
autocomplete `pulse.*` keys, link them back to the record
field, and surface the @DefaultValue / Javadoc.
MUST live here: declaring annotationProcessorPaths at all
switches javac off classpath-based processor discovery, so
a `<dependency><optional>true</optional></dependency>` is
ignored. The explicit <version> is required because
annotationProcessorPaths doesn't honour dependencyManagement. -->
<path>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>${spring.boot.version}</version>
</path>
<path>
<groupId>com.google.errorprone</groupId>
<artifactId>error_prone_core</artifactId>
<version>${error-prone-core.version}</version>
</path>
<path>
<groupId>com.uber.nullaway</groupId>
<artifactId>nullaway</artifactId>
<version>${nullaway.version}</version>
</path>
</annotationProcessorPaths>
<!-- @formatter:off -->
<compilerArgs>
<arg>-parameters</arg>
<arg>-XDcompilePolicy=simple</arg>
<arg>--should-stop=ifError=FLOW</arg>
<arg>-XDaddTypeAnnotationsToSymbol=true</arg>
<arg>-Xplugin:ErrorProne -XepExcludedPaths:.*/src/test/.* -Xep:NullAway:ERROR -XepOpt:NullAway:OnlyNullMarked -XepOpt:NullAway:JSpecifyMode=true</arg>
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED</arg>
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED</arg>
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED</arg>
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED</arg>
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED</arg>
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED</arg>
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED</arg>
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED</arg>
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED</arg>
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED</arg>
</compilerArgs>
<!-- @formatter:on -->
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
<configuration>
<!-- `@{argLine}` threads through the jacoco:prepare-agent output so
JaCoCo's -javaagent isn't clobbered. `-javaagent:${mockito}` pins
Mockito as a proper agent on JDK 21+ so it stops self-attaching
(Mockito 5 warning "This will no longer work in future releases of
the JDK."). `-XX:+EnableDynamicAgentLoading` silences the byte-buddy
"A Java agent has been loaded dynamically" warning in forked JVMs.
Pulse ships both the Log4j2 starter (default) and an optional
logback-classic dep so PulseLogbackEncoder can compile and be
unit-tested. Both bindings end up on the test classpath, so:
1. `LoggingSystem` forces Spring Boot's LoggingSystem auto-detection
to pick Log4j2 (else it would pick Logback first and fail to
re-bind SLF4J which is already wired to log4j-slf4j2-impl).
2. `slf4j.provider` tells SLF4J 2.0.9+ to skip its ServiceLoader
scan and bind directly to log4j-slf4j2-impl — suppresses the
"Class path contains multiple SLF4J providers" warning.
3. `slf4j.internal.verbosity=WARN` mutes SLF4J 2.0.10+'s own
"Attempting to load provider ..." INFO lines that otherwise
print once per test fork. -->
<argLine>@{argLine} -javaagent:${org.mockito:mockito-core:jar} -XX:+EnableDynamicAgentLoading</argLine>
<systemPropertyVariables>
<org.springframework.boot.logging.LoggingSystem>org.springframework.boot.logging.log4j2.Log4J2LoggingSystem</org.springframework.boot.logging.LoggingSystem>
<slf4j.provider>org.apache.logging.slf4j.SLF4JServiceProvider</slf4j.provider>
<slf4j.internal.verbosity>WARN</slf4j.internal.verbosity>
</systemPropertyVariables>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>3.2.5</version>
<configuration>
<!-- Mirror surefire — Mockito javaagent + silenced SLF4J internals
+ Log4j2 pin during integration tests.
IT-only twist: WireMock (used by TimeoutBudgetEndToEndIT) ships a
standalone fat-jar with a relocated SLF4J under `wiremock.org.slf4j`.
Setting `-Dslf4j.provider=...` makes that shaded loader try to cast
our non-shaded provider to its own relocated interface and blow up
with a ClassCastException at fork startup. We solve it by:
1. Excluding logback-classic from the IT classpath (only used by
PulseLogbackEncoderTest, which runs under surefire). With only
log4j-slf4j2-impl present, SLF4J's ServiceLoader has nothing
to disambiguate — no "multiple providers" warning, no need to
pin slf4j.provider.
2. Therefore NOT setting `slf4j.provider`, which keeps WireMock's
shaded SLF4J quiet too. -->
<argLine>@{argLine} -javaagent:${org.mockito:mockito-core:jar} -XX:+EnableDynamicAgentLoading</argLine>
<systemPropertyVariables>
<org.springframework.boot.logging.LoggingSystem>org.springframework.boot.logging.log4j2.Log4J2LoggingSystem</org.springframework.boot.logging.LoggingSystem>
<slf4j.internal.verbosity>WARN</slf4j.internal.verbosity>
</systemPropertyVariables>
<classpathDependencyExcludes>
<classpathDependencyExclude>ch.qos.logback:logback-classic</classpathDependencyExclude>
<classpathDependencyExclude>ch.qos.logback:logback-core</classpathDependencyExclude>
</classpathDependencyExcludes>
</configuration>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- Spotless — auto-applies Palantir Java Format on every compile.
Pattern lifted from arun0009/idempotent so contributors moving
between the two repos have identical muscle memory. Reformats are
applied (not just checked) so PRs always land clean. -->
<plugin>
<groupId>com.diffplug.spotless</groupId>
<artifactId>spotless-maven-plugin</artifactId>
<version>${spotless.version}</version>
<configuration>
<formats>
<format>
<includes>
<include>*.md</include>
<include>.gitignore</include>
</includes>
<trimTrailingWhitespace/>
<endWithNewline/>
<indent>
<tabs>true</tabs>
<spacesPerTab>2</spacesPerTab>
</indent>
</format>
</formats>
<java>
<cleanthat/>
<palantirJavaFormat/>
<importOrder>
<order>,javax|java,\#</order>
</importOrder>
<removeUnusedImports/>
<formatAnnotations/>
</java>
<pom>
<includes>
<include>pom.xml</include>
</includes>
<sortPom>
<encoding>UTF-8</encoding>
<keepBlankLines>true</keepBlankLines>
<nrOfIndentSpace>4</nrOfIndentSpace>
<indentBlankLines>false</indentBlankLines>
<indentAttribute>schemaLocation</indentAttribute>
<expandEmptyElements>false</expandEmptyElements>
<sortProperties>true</sortProperties>
</sortPom>
</pom>
</configuration>
<executions>
<execution>
<goals>
<goal>apply</goal>
</goals>
<phase>compile</phase>
</execution>
</executions>
</plugin>
<!-- JaCoCo — branch coverage gate. Fails the build under threshold.
Skipped when tests are skipped (e.g. release `mvn -DskipTests deploy`)
because the gate has no fresh execution data to evaluate. -->
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.14</version>
<configuration>
<skip>${skipTests}</skip>
</configuration>
<executions>
<execution>
<id>prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>prepare-agent-it</id>
<goals>
<goal>prepare-agent-integration</goal>
</goals>
</execution>
<execution>
<id>report</id>
<goals>
<goal>report</goal>
<goal>report-integration</goal>
</goals>
<phase>verify</phase>
</execution>
<execution>
<id>merge-exec-data</id>
<goals>
<goal>merge</goal>
</goals>
<phase>verify</phase>
<configuration>
<fileSets>
<fileSet>
<directory>${project.build.directory}</directory>
<includes>
<include>jacoco.exec</include>
<include>jacoco-it.exec</include>
</includes>
</fileSet>
</fileSets>
<destFile>${project.build.directory}/jacoco-merged.exec</destFile>
</configuration>
</execution>
<execution>
<id>merged-report</id>
<goals>
<goal>report</goal>
</goals>
<phase>verify</phase>
<configuration>
<dataFile>${project.build.directory}/jacoco-merged.exec</dataFile>
<outputDirectory>${project.build.directory}/site/jacoco-merged</outputDirectory>
</configuration>
</execution>
<execution>
<id>coverage-gate</id>
<goals>
<goal>check</goal>
</goals>
<phase>verify</phase>
<configuration>
<!-- Gate on merged unit + integration coverage. Thresholds sit just
below the actual measured coverage so the gate ratchets quality
forward instead of permitting silent regressions, while leaving
enough headroom that small refactors don't trip the build. The
excludes below remove auto-config wiring and the test-slice
ship-along (covered by integration, not by unit, because the
wiring graph IS the contract). -->
<dataFile>${project.build.directory}/jacoco-merged.exec</dataFile>
<rules>
<rule>
<element>BUNDLE</element>
<limits>
<limit>
<counter>LINE</counter>
<value>COVEREDRATIO</value>
<minimum>0.70</minimum>
</limit>
<limit>
<counter>BRANCH</counter>
<value>COVEREDRATIO</value>
<minimum>0.50</minimum>
</limit>
</limits>
</rule>
</rules>
<excludes>
<exclude>io/github/arun0009/pulse/test/**</exclude>
<exclude>io/github/arun0009/pulse/startup/**</exclude>
</excludes>
</configuration>
</execution>
</executions>
</plugin>
<!-- CycloneDX — generate an SBOM at build time so consumers can audit
supply-chain risk. Required by every enterprise OSS review in 2026. -->
<plugin>
<groupId>org.cyclonedx</groupId>
<artifactId>cyclonedx-maven-plugin</artifactId>
<version>2.9.1</version>
<configuration>
<projectType>library</projectType>
<schemaVersion>1.5</schemaVersion>
<includeBomSerialNumber>true</includeBomSerialNumber>
<includeCompileScope>true</includeCompileScope>
<includeProvidedScope>true</includeProvidedScope>
<includeRuntimeScope>true</includeRuntimeScope>
<includeSystemScope>true</includeSystemScope>
<includeTestScope>false</includeTestScope>
<outputName>pulse-${project.version}-sbom</outputName>
</configuration>
<executions>
<execution>
<id>build-sbom</id>
<goals>
<goal>makeAggregateBom</goal>
</goals>
<phase>package</phase>
</execution>
</executions>
</plugin>
</plugins>
</build>
<profiles>
<!-- mvn -Prelease deploy → stages artifacts to target/staging-deploy.
Then `mvn -Prelease jreleaser:deploy` (with JRELEASER_* env vars set)
signs and publishes to Maven Central via the Sonatype Central Portal.
Configuration mirrors arun0009/idempotent so the same GitHub Actions
secrets and GPG key apply. -->
<profile>
<id>release</id>
<properties>
<altDeploymentRepository>local::file:./target/staging-deploy</altDeploymentRepository>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.jreleaser</groupId>
<artifactId>jreleaser-maven-plugin</artifactId>
<version>${jreleaser-maven-plugin.version}</version>
<inherited>false</inherited>
<configuration>
<jreleaser>
<signing>
<active>ALWAYS</active>
<armored>true</armored>
</signing>
<deploy>
<maven>
<mavenCentral>
<sonatype>
<active>ALWAYS</active>
<url>https://central.sonatype.com/api/v1/publisher</url>
<stagingRepositories>target/staging-deploy</stagingRepositories>
</sonatype>
</mavenCentral>
</maven>
</deploy>
</jreleaser>
</configuration>
</plugin>
<plugin>
<artifactId>maven-source-plugin</artifactId>
<version>${maven-source-plugin.version}</version>
<executions>
<execution>
<id>attach-source</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-javadoc-plugin</artifactId>
<version>${maven-javadoc-plugin.version}</version>
<configuration>
<!-- Strict-but-pragmatic. doclint=all,-missing flags broken @link / malformed
HTML / etc. (the things consumers actually see) without firing on the
deliberate omissions of @param/@return on internal helpers. -->
<doclint>all,-missing</doclint>
<quiet>true</quiet>
<doctitle>Pulse ${project.version} API</doctitle>
<windowtitle>Pulse ${project.version}</windowtitle>
<bottom><![CDATA[Copyright © {currentYear} Pulse contributors. Released under the MIT License.]]></bottom>
</configuration>
<executions>
<execution>
<id>attach-javadoc</id>
<goals>
<goal>jar</goal>
</goals>
<phase>package</phase>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
<!-- mvn -Pbench -DskipTests test-compile exec:exec → runs JMH benchmarks.
JMH itself is test-scoped above so the @Benchmark sources always
compile; this profile only adds the runner. -->
<profile>
<id>bench</id>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.5.0</version>
<configuration>
<executable>java</executable>
<classpathScope>test</classpathScope>
<arguments>
<argument>-classpath</argument>
<classpath/>
<argument>org.openjdk.jmh.Main</argument>
<argument>-rf</argument>
<argument>json</argument>
<argument>-rff</argument>
<argument>${project.build.directory}/jmh-result.json</argument>
</arguments>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>