skip to content

How would you configure a custom sampling strategy in Spring Boot — for example, always sampling a critical endpoint but rate-limiting the rest?

level: seniorimportance: should knowfreq 35%

answer

  1. property = simple; Sampler bean = custom
  2. ConditionalOnMissingBean backoff
  3. Brave: RateLimitingSampler.create(n)
  4. OTel: parentBased(traceIdRatioBased)
  5. custom bean overrides the property

basics

~10 s

For simple cases set management.tracing.sampling.probability. For custom logic, define your own Sampler bean (Brave's brave.sampler.Sampler or OpenTelemetry's io.opentelemetry.sdk.trace.samplers.Sampler), which overrides the property. Brave offers RateLimitingSampler and per-path samplers you can compose.

solid answer

~40 s

Spring Boot auto-configures a Sampler from management.tracing.sampling.probability, but you can replace it by defining your own Sampler bean, which the auto-config backs off from. With the Brave bridge you supply a brave.sampler.Sampler — e.g. RateLimitingSampler.create(tracesPerSecond) for predictable cost, or a custom Sampler that inspects the operation and returns true for critical paths. With the OpenTelemetry bridge you supply an io.opentelemetry.sdk.trace.samplers.Sampler, typically Sampler.parentBased(root) so upstream decisions are honored, wrapping a ratio or a rule-based delegate. The key ideas: the property is the simple knob; a Sampler bean is the escape hatch; use ParentBased/parent-honoring wrappers so you don't break distributed trace coherence; and prefer rate limiting when you need bounded cost under traffic spikes.

code

java · 31 lines
java
import io.opentelemetry.sdk.trace.samplers.Sampler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
class TracingSamplingConfig {

    // OpenTelemetry bridge: always sample /checkout, otherwise 5%,
    // and honor any upstream sampling decision via parentBased(...).
    @Bean
    Sampler otelSampler() {
        Sampler fallback = Sampler.traceIdRatioBased(0.05);
        Sampler rules = new Sampler() {
            @Override public io.opentelemetry.sdk.trace.samplers.SamplingResult shouldSample(
                    io.opentelemetry.context.Context ctx, String traceId, String name,
                    io.opentelemetry.api.trace.SpanKind kind,
                    io.opentelemetry.api.common.Attributes attrs,
                    java.util.List<io.opentelemetry.api.trace.LinkData> links) {
                String route = attrs.get(
                    io.opentelemetry.api.common.AttributeKey.stringKey("http.route"));
                if ("/checkout".equals(route)) {
                    return io.opentelemetry.sdk.trace.samplers.SamplingResult.recordAndSample();
                }
                return fallback.shouldSample(ctx, traceId, name, kind, attrs, links);
            }
            @Override public String getDescription() { return "criticalRouteSampler"; }
        };
        // Defining this bean overrides management.tracing.sampling.probability.
        return Sampler.parentBased(rules); // honor upstream sampled flag for existing traces
    }
}

go deeper

for a junior

Know the property is the simple option; custom needs a bean.

for a middle

Name the two bean types (Brave vs OTel Sampler) and that the bean overrides the property.

for a senior

Compose rate limiting + route rules + parentBased correctly for the active bridge.

for a principal

Decide where sampling logic belongs (in-app Sampler vs collector tail sampling) across a fleet.

**Layers of configuration.** 1. **Property (simplest).** `management.tracing.sampling.probability=0.2` — one global ratio. Good enough for most services. 2. **Custom `Sampler` bean (escape hatch).** Spring Boot's tracing auto-configuration creates a default `Sampler` only if you don't define one (`@ConditionalOnMissingBean`). Declaring your own bean overrides the property-driven default entirely, so you own the logic. **Brave bridge (`micrometer-tracing-bridge-brave`).** The bean type is `brave.sampler.Sampler`. - `Sampler.ALWAYS_SAMPLE` / `Sampler.NEVER_SAMPLE` — constants. - `CountingSampler.create(rate)` — probabilistic. - `RateLimitingSampler.create(tracesPerSecond)` — caps traces/second, decoupling cost from traffic (great for spike safety). - A custom `Sampler` overriding `isSampled(long traceId)` for trace-ID-based logic. - For **per-request/per-path** decisions, Brave uses `SamplerFunction<HttpRequest>` on the HTTP tracing handler — this can look at the path/method and decide. **OpenTelemetry bridge (`micrometer-tracing-bridge-otel`).** The bean type is `io.opentelemetry.sdk.trace.samplers.Sampler`. - `Sampler.alwaysOn()`, `Sampler.alwaysOff()`, `Sampler.traceIdRatioBased(ratio)`. - `Sampler.parentBased(root)` — **honor the upstream sampled flag**, falling back to `root` for new traces. This is what preserves distributed-trace coherence; Spring Boot uses a parent-based wrapper by default. - A custom `Sampler` implementing `shouldSample(...)` to inspect span name/attributes and return `RECORD_AND_SAMPLE` for critical operations, delegating otherwise. **Design guidance.** - **Wrap in parent-based** so you don't re-decide on requests that already carry an upstream sampled flag — otherwise you fragment traces. - **Rate limiting** gives bounded cost under spikes; probability does not. - **Route-aware** sampling (always-on critical paths, low on health checks/hot reads) targets your budget where it matters. - When you define a custom `Sampler` bean, `management.tracing.sampling.probability` is **ignored** (your bean wins) — document that so nobody is surprised the property 'does nothing'. **Gotchas.** - Bean type must match the active bridge — a Brave `Sampler` bean does nothing under the OTel bridge and vice versa. - Forgetting `parentBased` on the OTel side means downstream services may override upstream decisions, breaking coherence. - Custom samplers that inspect attributes only see attributes available *at span start*; late-set attributes (like final status) aren't available — that's again the head-based limitation, and true outcome-based selection belongs in a tail-sampling collector.

  • Once you define a custom Sampler bean, does management.tracing.sampling.probability still apply?
    No. The auto-configured default sampler is conditional on no user-defined Sampler; your bean replaces it, so the property is ignored. Document that to avoid confusion.
  • Why wrap your OpenTelemetry sampler in Sampler.parentBased(...)?
    So the service honors an upstream service's existing sampling decision (the incoming sampled flag) and only applies your local logic for brand-new root traces, keeping distributed traces coherent instead of re-deciding at every hop.
  • Which sampler gives predictable cost during a traffic spike?
    Brave's RateLimitingSampler (or an equivalent rate limiter), because it caps traces per second regardless of load. A probability/ratio sampler scales export volume with traffic, so a spike multiplies cost.

saying these in an interview costs you the question

  • Providing a Brave Sampler bean while the OTel bridge is active (or vice versa)
  • Expecting the probability property to still work after defining a custom Sampler
  • Omitting parentBased and thereby breaking distributed trace continuity
  • Trying to sample on final HTTP status in a head-based Sampler (not available at span start)

context