Skip to content

Spring Cloud /actuator/refresh corrupts live SentryOptions #6369

Open

Description

@rmie

Integration

sentry-spring-boot-4

Java Version

JVM 21 (OpenJ9)

Other Error Monitoring Solution

No

Other Error Monitoring Solution Name

No response

Version

8.61.1

Steps to Reproduce

Environment

  • io.sentry:sentry-spring-boot-4-starter 8.61.1 (sentry + sentry-spring-7 + sentry-logback 8.61.1)
  • Spring Boot 4.1.2, Spring Cloud 2025.1.x (config client + refresh endpoint enabled)

Summary

SentryProperties extends SentryOptions, and
SentryAutoConfiguration$HubConfiguration.sentryHub passes the bound
@ConfigurationProperties bean itself to Sentry.init(options). The bean
therefore is the live SentryOptions held by the initialized scopes.

When Spring Cloud's /actuator/refresh fires EnvironmentChangeEvent,
ConfigurationPropertiesRebinder rebinds all @ConfigurationProperties
beans — including SentryProperties. Because the bean is the live options
object, the rebind corrupts the running SDK:

Repro

  1. Spring Boot 4 app with sentry-spring-boot-4-starter, Spring Cloud Config
    client, management.endpoints.web.exposure.include covering refresh.
  2. Configure sentry.dsn, sentry.environment, sentry.servername via a
    remote property source (spring-cloud-config).
  3. Start → events arrive with correct environment/server_name.
  4. POST /actuator/refresh (even with no config change).
  5. Subsequent events carry environment: production,
    server_name: <hostname> — and the refresh request itself throws the
    retrieveParsedDsn NPE.

Expected Result

sentry.* should be resilient to configuration rebinding. Either:

  • sentryHub copies the bound values into a fresh SentryOptions before
    Sentry.init — the live options then stay immutable after init, and a
    refresh simply doesn't apply new values (no corruption), or
  • the autoconfiguration listens for EnvironmentChangeEvent /
    RefreshScopeRefreshedEvent and performs a proper Sentry.init with a
    fully-bound SentryProperties — making refresh actually work.

Actual Result

After POST /actuator/refresh — reproducible even with no config change:

  1. The request handling /actuator/refresh itself crashes during
    SentryTracer.finish — the live SentryOptions.dsn is transiently
    null while ConfigurationPropertiesRebinder rebinds the
    SentryProperties bean:

    java.lang.NullPointerException / IllegalArgumentException
      at io.sentry.util.Objects.requireNonNull(Objects.java:13)
      at io.sentry.Dsn.<init>(Dsn.java:54)
      at io.sentry.SentryOptions.lambda$new$0(SentryOptions.java:113)
      at io.sentry.util.LazyEvaluator.getValue(LazyEvaluator.java:39)
      at io.sentry.SentryOptions.retrieveParsedDsn(SentryOptions.java:831)
      at io.sentry.Baggage.setValuesFromTransaction(Baggage.java:529)
      at io.sentry.SentryTracer.finish(SentryTracer.java:292)
      at io.sentry.spring7.tracing.SentryTracingFilter.doFilterWithTransaction(SentryTracingFilter.java:172)
    
  2. dsn recovers after rebind, so telemetry keeps flowing to the correct
    project — but environment and serverName stay null on the live
    options. All subsequent events are emitted with environment = null
    (Sentry server assigns the default production) and server_name = <container hostname> (auto-detection fallback) — while
    /actuator/configprops still shows sentry.environment/sentry.servername
    correctly bound on the (new) SentryProperties bean.

  3. Only a JVM restart restores correct environment/server_name.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions