Every Spring Boot developer hits the same wall eventually. You've got configuration scattered across property files, environment variables, and hardcoded defaults. You need a clean way to pull those values into your beans without writing repetitive boilerplate. That's exactly what the @Value annotation was built for.
This guide walks through everything you can do with @Value: inline values, property file references, SpEL expressions, and the gotchas that'll save you debugging time.
The short version:
- Inline values work directly with @Value("literal")
- Property files use ${property.name} syntax
- SpEL handles complex expressions with #{…}
- Default values protect against missing properties
- Lists and maps need special handling with comma-separated values or SpEL
How @Value Works
The annotation lives in org.springframework.beans.factory.annotation. You slap it on a field inside any Spring-managed bean, and the framework injects whatever value you specify. The source can be hardcoded text, a property from your application.properties, a system property, an environment variable, or a SpEL expression.
Here's the simplest case:
“`java
@Value("30")
Private Integer duration;
@Value("4.5")
Private Float rating;
@Value("true")
Private Boolean isTrending;
“`
These are inline values. They work, but they defeat the purpose of externalized configuration. You'd normally pull these from property files instead.
Pulling Values from Property Files
Externalize your configuration to application.properties or application.yml:
“`
Course.title=Advanced Spring Boot Techniques
Course.duration=30
Course.rating=4.5
Course.page_views=10000000000
Course.trending=true
“`
Reference them in your bean using ${…} syntax:
“`java
@Value("${course.title}")
Private String title;
@Value("${course.duration}")
Private int duration;
@Value("${course.rating}")
Private float rating;
“`
You can concatenate multiple properties in a single annotation. Say you want the title and rating together as a display string:
“`java
@Value("${course.title} (${course.rating})")
Private String titleAndRating;
“`
Or combine them with static text:
“`java
@Value("${course.title} – ${course.duration} min")
Private String titleAndDuration;
“`
Default Values: Your Safety Net
Production systems break when properties go missing. @Value handles this with default value syntax: ${property.name:default}.
If course.review isn't defined anywhere, you get "No Reviews Yet" instead of a startup failure:
“`java
@Value("${course.review:No Reviews Yet}")
Private String review;
“`
This pattern is essential for optional configuration. Users can override defaults when they need to, and the system works fine when they don't.
Working with Lists and Maps
Lists of comma-separated values work directly in newer Spring versions:
“`java
@Value("${course.tags}")
Private List<String> tags;
“`
With application.properties containing:
“`
Course.tags=Java, Spring, Spring Boot, Annotation
“`
Older Spring versions require SpEL to split the string:
“`java
@Value("#{'${course.tags}'.split(',')}")
Private List<String> tags;
“`
Need just the first element? SpEL supports array access:
“`java
@Value("#{'${course.tags}'.split(',')[0]}")
Private String firstTag;
“`
Maps need more care. Property files require single quotes around both keys and values:
“`
Course.keyword_count='keyword1': '12', 'keyword2': '44', 'keyword3': '85', 'keyword4': '100'
“`
Inject the full map with SpEL:
“`java
@Value("#{${course.keyword_count}}")
Private Map<String, Integer> keywordCountMap;
“`
Extract a single key using dot notation or bracket notation:
“`java
@Value("#{${course.keyword_count}.keyword1}")
Private Integer firstKeywordCount;
@Value("#{${course.keyword_count}['keyword2']}")
Private Integer secondKeywordCount;
“`
When you're not sure a key exists, use the safe navigation approach that returns null instead of throwing:
“`java
@Value("#{${course.keyword_count}['unknownKey']}")
Private Integer unknownKeywordCount;
“`
You can even filter map entries during injection. Grab only keywords with count over 50:
“`java
@Value("#{${course.keyword_count}.?[value > 50]}")
Private Map<String, Integer> filteredKeywords;
“`
Why This Matters
@Value is the workhorse of Spring configuration. It handles the simple cases cleanly without forcing you into heavyweight patterns. You can inject a database timeout, an API endpoint, or a feature flag in one line.
The trade-off is simplicity. As your configuration grows, @Value fields multiply. A class with fifteen @Value injections becomes harder to test and reason about. That's when @ConfigurationProperties classes make sense. They group related settings into typed objects with validation support.
SpEL adds real power. You can reference system properties, environment variables, and even call methods within @Value expressions. Just remember that this power comes with debugging complexity. An error in a SpEL expression might not surface until runtime.
Practical Example
Say you're building a notification service with rate limiting. Instead of hardcoding limits, load them from configuration:
“`java
@Component
Public class NotificationService {
@Value("${notification.rate.limit:100}")
Private int maxNotifications;
@Value("${notification.enabled:true}")
Private boolean notificationsEnabled;
@Value("${notification.batch.size:10}")
Private int batchSize;
Public void sendNotifications(List<String> recipients) {
If (!notificationsEnabled) {
Return;
}
For (int i = 0; i < recipients.size(); i += batchSize) {
If (i >= maxNotifications) {
Break;
}
// Send batch
}
}
}
“`
Operations can now tune limits without code changes. Set notification.rate.limit=200 in production, keep the default 100 in development.
What to Watch Next
Spring Boot 3 strengthened type-safe configuration with constructor binding for @ConfigurationProperties. The @Value annotation stays useful for simple injections, but if you're managing complex nested configuration, explore the newer patterns. They're more testable and catch configuration errors at startup rather than runtime.
Key takeaways
- @Value handles inline values, properties, and SpEL in one consistent syntax
- Always provide defaults for optional configuration to prevent startup failures
- Use SpEL for lists, maps, dates, and computed expressions
- Extract specific map keys with .key or ['key'] notation
- Move to @ConfigurationProperties when configuration complexity outgrows @Value
Frequently asked questions
What's the difference between @Value and @ConfigurationProperties?
@Value injects individual values into fields; @ConfigurationProperties binds multiple properties to a typed object with validation support. @ConfigurationProperties scales better for complex configuration and is easier to test.
Can @Value inject environment variables?
Yes. You can reference them directly in SpEL expressions or map them in property files and reference those properties instead.
How do I inject a Date with @Value?
There's no direct support. Use SpEL with SimpleDateFormat: @Value("#{new java.text.SimpleDateFormat('yyyyMMdd').parse('20210530')}") for Date, or T(java.time.LocalDate).parse() for LocalDate and LocalDateTime.
What happens if a property doesn't exist and I didn't set a default?
The application fails to start with a BeanCreationException. Always provide defaults for optional properties.
Can I use @Value with constructor injection instead of field injection?
Yes. Put @Value on a constructor parameter to inject the value during bean construction, which is the preferred approach for testability.