@Value Annotation Basics
The @Value annotation (introduced in 10.0.0, package org.apache.juneau.commons.inject) is a declarative way to inject configuration values into beans created by BeanInstantiator — constructor parameters, setter parameters, and fields.
It complements @Inject:
@Injectrequests a bean from the bean store by type.@Valuerequests a configuration value from Settings via the VarResolver.DEFAULT resolver.
The two are mutually exclusive on the same injection site — applying both raises
BeanCreationException.
The ${...} shortcut
Inside a @Value expression (or anywhere you can write SVL), ${key} is shorthand for $P{key} —
the PropertyVar that
looks up a unified property by name from Settings. The shortcut also accepts a Spring-style
default after a colon:
${some.key} — required key, missing → empty string
${some.key:fallback} — default-when-missing
${section/sub.key} — nested property names (Config sections, etc.)
${a.b:${other:none}} — nested expansion (defaults composed)
This is purely surface syntax — the tokenizer rewrites ${...} to $P{...} before resolution.
Everything $P{...} does (recursive resolution, default values, env-var fallback through chained
sources) works identically with ${...}. See
Simple Variable Language Basics and
Variable Basics for the underlying mechanics.
Resolution order (precedence)
For most @Value sites, resolution goes through the process-wide Settings.get(name) chain:
Settings walks every registered PropertySource in reverse insertion order — the
most-recently-added source wins. The default chain (built up at startup) looks like:
PropertySourceProviderSPI sources added at process startup (e.g. classpathjuneau.cfgviaConfigPropertySourceProvider). These register first and therefore sit at the bottom of the priority list.- Per-microservice
Config—org.apache.juneau.microservice.Microserviceinstalls aConfigPropertySourcefor its ownConfigin its constructor. - Spring
Environmentbridge — whenjuneau-rest-server-springboot'sSpringBeanStoreis constructed, it installs aSpringEnvironmentPropertySourceso${spring.app.key}resolves againstapplication.yaml, env vars, command-line args, and any custom SpringPropertySource. - Built-in
Settingssources — system properties, environment variables,.envfile, argument list, JAR manifest entries.
When the @Value site is on a bean that the REST runtime instantiates (resource bean fields,
constructor parameters, request-scoped bean fields), the resolution order grows one tier —
the resource's @Rest(config=...) Config participates
between the Settings override layer and the chain above.
Because all sources are checked, the same @Value expression resolves identically under a
microservice, a Spring Boot app, or a bare REST resource — the origin of the value changes, the
expression doesn't.
Field injection
import org.apache.juneau.commons.inject.Value;
public class MyResource {
@Value("${myapp.title:Untitled}")
private String title;
@Value("${myapp.maxPageSize:50}")
private int maxPageSize;
}
Settings.toType(...) coerces the resolved string to the target type. Coercion supports:
- Primitives and their boxed wrappers.
java.timetypes (Instant,Duration,Period,LocalDate, etc.) — any type with aparse(CharSequence)orparse(String)static method.Optional<T>—Optional.empty()is injected when the key resolves tonullor the empty string; otherwiseOptional.of(coerced).- Any type with a single-string-argument constructor.
Constructor + setter parameter injection
@Value works on constructor parameters and on setter (and any method) parameters:
public class JwtValidator {
private final Duration ttl;
public JwtValidator(@Value("${jwt.cache.ttl:PT5M}") Duration ttl) {
this.ttl = ttl;
}
@Inject
public void setKeyResolver(KeyResolver keys) { ... } // @Inject path — bean lookup
public void setMaxClock(@Value("${jwt.clock.skew:PT30S}") Duration skew) { ... }
}
Supplier<String> field type — re-evaluating reads (10.0.0)
Declaring a @Value field as Supplier<String> instead of String makes the SVL expression
re-evaluate on every .get() call against a fresh VarResolverSession. No annotation flag is
required — the field type IS the opt-in signal.
public class JwtValidator {
// String field: resolves ONCE at bean construction. Captures whatever Settings/PropertyVar
// returned at injection time and holds that value for the bean's lifetime.
@Value("${jwt.cache.ttl:PT5M}")
String ttl;
// Supplier<String> field: re-evaluates on every .get() call against a fresh session.
// Picks up live Settings.set(...) updates, file-watch reloads, Spring env changes, etc.
@Value("${jwt.cache.ttl:PT5M}")
Supplier<String> liveTtl;
}
The returned Supplier is threadsafe — each .get() opens its own VarResolverSession,
so it can be cached on shared fields or handed across threads. (Internally this delegates to
VarTemplate.asSupplierWithFreshSessions(...).)
Literal-only expressions (no variables) take a constant-folding fast path: the resolved string
is captured once at compile time and every .get() returns the same cached value with no
session overhead.
Composition with #{...} functions
Combining the Supplier<String> field type with the #{...} function catalog produces concise
live-factory patterns:
// Request-ID factory — fresh UUID per request.
@Value("#{uuid()}")
Supplier<String> requestId;
// Idempotency key for a downstream API — random per call.
@Value("#{concat(ord-, #{randomString(12)})}")
Supplier<String> idempotencyKey;
// Jittered retry delay (ISO 8601 duration with 100–500ms jitter).
@Value("PT0.#{randomInt(100, 500)}S")
Supplier<String> retryDelayIso;
// Live ISO timestamp on every call.
@Value("#{now()}")
Supplier<String> nowIso;
// Hot-reload config — picks up Settings.set(...) without bean re-creation.
@Value("${myapp.cache.policy:write-through}")
Supplier<String> cachePolicy;
See Simple Variable Language — the VarTemplate compiled-template
API, which describes how
VarResolver.resolveSupplier(...) pairs with @Value Supplier<String> fields for live-reload.
Spring @Value compatibility
For consumers migrating from Spring Boot, org.springframework.beans.factory.annotation.Value
is detected by fully-qualified name (JsrSupport.SPRING_VALUE) — juneau-commons doesn't
declare a compile-time dependency on Spring. The same resolution chain applies, so the following
two beans are functionally identical inside a Juneau bean store:
public class A {
@org.apache.juneau.commons.inject.Value("${jwt.cache.ttl:PT5M}")
Duration ttl;
}
public class B {
@org.springframework.beans.factory.annotation.Value("${jwt.cache.ttl:PT5M}")
Duration ttl;
}
Spring Boot bridge (juneau-rest-server-springboot)
Inside a Spring Boot application, SpringBeanStore automatically installs a
SpringEnvironmentPropertySource
the first time it's constructed (typically by SpringRestServlet.createBeanStore(...)). This
means application.yaml keys are reachable from a Juneau @Value:
# application.yaml
myapp:
title: My Awesome App
cache:
ttl: PT10M
@Rest
public class MyResource extends BasicSpringRestServlet {
@Value("${myapp.title:Untitled}")
private String title;
@Value("${myapp.cache.ttl:PT5M}")
private Duration cacheTtl;
}
The bridge is removed when SpringBeanStore.clear() is called — important for tests that build
many transient Spring contexts in the same JVM.
Per-resource @Rest(config=...) integration
When a REST resource declares @Rest(config="my.cfg"), the resolved Config is wired into the
resource's BeanStore as a PropertySource
bean named "rest.config". Any @Value("${some-key}") site on the resource bean (or on a
request-scoped bean whose BeanStore parent-walks to the resource's) consults this scoped
PropertySource between the Settings local/global overrides and the global Settings sources
chain.
Resolution order for resource-scoped @Value sites
Settings.setLocal(...)/Settings.setGlobal(...)overrides — still win. The test override contract is preserved (overrides beat resource Configs).- The resource's
@Rest(config=...)Config(s) — via the per-BeanStorePropertySourcebean. - The process-wide
Settings.get(name)chain — reverse-walks the registered sources from the resolution-order section above. - The
@Valuedefault branch — the${key:default}literal.
Concrete example
api.cfg on the classpath / cwd:
api.key = secret-A
api.url = https://a.example.org/
[section]
nested = nested-value
@Rest(config="api.cfg")
public class MyResource extends BasicRestServlet {
@Value("${api.key}") // → "secret-A"
String apiKey;
@Value("${section/nested}") // → "nested-value"
String nested;
@Value("${unknown:fallback}") // → "fallback" (key absent, default used)
String fallback;
@Value("${java.home}") // → System property (falls through to Settings)
String javaHome;
}
Isolation guarantee
Each RestContext carries its own BeanStore with its own "rest.config" bean. Resource A's
@Value("${foo}") resolves against A.cfg, Resource B's @Value("${foo}") resolves against
B.cfg — the two never see each other's Config. The same isolation extends to
MockRestClient's static RestContext cache and to long-lived Spring Boot contexts because
the registration lives on the per-resource BeanStore, not on the process-wide singleton.
Inheritance (child wins, parent fills gaps)
When @Rest(config="parent.cfg") on ParentResource and @Rest(config="child.cfg") on
ChildResource extends ParentResource declare different config names, the child's keys win on
collision and the parent's keys fill the gaps:
# parent.cfg
parent.only = parent-value
shared = parent-shared
# child.cfg
child.only = child-value
shared = child-shared
@Rest(config="parent.cfg")
public class ParentResource extends BasicRestServlet {
@Value("${parent.only}") String parentOnly;
@Value("${shared}") String shared;
}
@Rest(config="child.cfg")
public class ChildResource extends ParentResource {}
A ChildResource instance sees child-value for child.only, parent-value for parent.only
(child's Config doesn't define it — parent fills the gap), and child-shared for
shared (child wins on collision).
setLocal / setGlobal still win
The resource-scoped Config is consulted after the Settings override stores, so existing
test fixtures that call Settings.get().setGlobal("api.key", "from-test") continue to take
precedence over api.cfg — matching the established override contract.
Supplier<String> re-evaluates against the scoped BeanStore
@Value Supplier<String> field types compose with the resource-scoped chain: the BeanStore
reference is captured (by-reference, not snapshotted) by the supplier, so every .get() call
re-evaluates against the current state of the resource's PropertySources. This means hot-
reload of the resource Config (or Settings.set(...) of an override key) takes effect on the
next .get() call without rebuilding the resource bean.
Classpath-default Config (juneau-config)
The juneau-config module ships a META-INF/services entry for
ConfigPropertySourceProvider.
At startup, Settings.useServiceLoader() discovers the provider and registers a lazy bridge to
Config.getSystemDefault(). The resolution is deferred to the first lookup so it can't trigger
class-init cycles, and the bridge is silent when no classpath default juneau.cfg exists.
This means a plain juneau.cfg on the classpath transparently feeds @Value("${section/key}")
without any explicit wiring.
Mutual exclusion with @Inject
@Value and @Inject on the same field, parameter, or setter is an error:
@Inject
@Value("${some.key}") // BeanCreationException at injection time
private String value;
The exception message identifies the offending site to make the conflict easy to fix.
See also
- Simple Variable Language Basics — the full SVL surface,
the
#{...}function catalog, and theVarTemplatecompiled-template API. - SVL Variables — list of built-in
$X{...}resolvers. - Variable Basics — how variables work inside a
Config. - org.apache.juneau.commons.inject.Value
- org.apache.juneau.commons.settings.Settings
- org.apache.juneau.commons.svl.vars.PropertyVar
- org.apache.juneau.commons.svl.VarTemplate
- org.apache.juneau.rest.server.springboot.SpringEnvironmentPropertySource