skip to content

How do @Profile expressions work — the !, & and | operators?

level: middleimportance: should knowfreq 45%

answer

  1. ! NOT, & AND, | OR
  2. no mixing & and | without parentheses
  3. array form = OR
  4. Profiles.of + acceptsProfiles
  5. since Spring 5.1

basics

~10 s

Since Spring 5.1, @Profile accepts a boolean expression over profile names: ! is NOT, & is AND, | is OR. Example @Profile("prod & cloud"). You must use parentheses if you mix & and |.

solid answer

~40 s

`@Profile` takes a String that Spring parses as a **profile expression**. Operators: `!` = NOT (`@Profile("!prod")` = any profile except prod), `&` = AND (`@Profile("prod & cloud")` = both active), `|` = OR (`@Profile("prod | staging")`). You **cannot mix `&` and `|` at the same level without parentheses** — e.g. `"prod & cloud | k8s"` is invalid; write `"(prod & cloud) | k8s"`. Passing several strings — `@Profile({"a","b"})` — is treated as OR of those names. Under the hood the expression is evaluated by `Profiles.of(...)` against `Environment.acceptsProfiles(...)`. This lets one bean depend on a combination of active profiles rather than a single name, which is handy for multi-dimensional environments (deployment target × cloud provider).

code

java · 11 lines
java
@Bean
@Profile("(prod | staging) & cloud")   // parentheses REQUIRED to mix
MetricsExporter cloudMetrics() { ... }

@Bean
@Profile("!prod")                        // everything except prod
DebugToolbar debugToolbar() { ... }

@Bean
@Profile({"dev", "test"})                // OR of the two
SeedDataLoader seeder() { ... }

go deeper

for a junior

Know at least the ! negation form; may not know & / |.

for a middle

Explain !, &, | and the array-as-OR form with an example.

for a senior

Add the parentheses-required-when-mixing rule and that it evaluates via Profiles.of/acceptsProfiles at registration time.

for a principal

Advise when expressions become unmaintainable and to prefer @ConditionalOnProperty or explicit config; note evaluation timing implications for dynamic reconfiguration.

## Profile expressions Before Spring 5.1, `@Profile` only accepted plain names (and a leading `!`). Since **5.1**, the value is a full boolean **profile expression** parsed by `org.springframework.core.env.Profiles.of(...)`. ### Operators - `!x` — **NOT**: active when profile `x` is *not* active. `@Profile("!prod")`. - `x & y` — **AND**: active only when *both* `x` and `y` are active. `@Profile("prod & cloud")`. - `x | y` — **OR**: active when *either* is active. `@Profile("dev | test")`. - Parentheses group sub-expressions: `@Profile("(prod | staging) & cloud")`. ### The mixing rule (key gotcha) You may **not** mix `&` and `|` at the same nesting level without parentheses. `@Profile("prod & cloud | k8s")` throws an `IllegalArgumentException` at parse time. Correct it explicitly: `@Profile("(prod & cloud) | k8s")` or `@Profile("prod & (cloud | k8s)")` — and the two mean different things, which is exactly why Spring forces you to be explicit. ### Array form = OR `@Profile({"a", "b"})` is equivalent to `@Profile("a | b")` — the array elements are OR-ed. Note you cannot put `&`/`|` operators *across* array elements meaningfully; keep a single-string expression when you need AND/NOT. ### Evaluation path `@Profile` is meta-annotated with `@Conditional(ProfileCondition.class)`. `ProfileCondition` reads the annotation's value(s), builds `Profiles.of(expressions)`, and calls `environment.acceptsProfiles(profiles)`. `acceptsProfiles` returns true if the expression matches the currently active set (or, for a bare positive name with nothing active, the default profiles). ### Interaction with 'default' `@Profile("default")` matches only when *no* profile is active. Combining it, e.g. `@Profile("default | dev")`, is legal but subtle: it matches when nothing is active OR when dev is active. ### When to use - Multi-axis environments: `region` × `tier`. - Excluding a bean everywhere but one place with `!`. - Keep expressions readable — deep expressions are a smell; consider `@Conditional`/`@ConditionalOnProperty` (Boot) for richer logic. ### Gotchas - Whitespace around operators is optional but conventional. - The expression is evaluated at **bean-definition registration time**, not at runtime — you can't flip profiles to re-evaluate a live context. - Negation `!` counts as an active-set check, not 'profile literally named !x'.

  • Is @Profile("a & b | c") valid?
    No. Mixing & and | at the same level without parentheses throws IllegalArgumentException. You must disambiguate: "(a & b) | c" or "a & (b | c)".
  • What does @Profile({"a","b"}) mean?
    OR — the bean is active if profile a OR profile b is active. The array elements are combined with logical OR.

saying these in an interview costs you the question

  • Thinking you can freely mix & and | without parentheses
  • Believing array form means AND
  • Assuming expressions are re-evaluated at runtime when profiles change
  • Claiming profile expressions existed before Spring 5.1

context