Building the Spring Boot Application
Configuration and Application Properties
আপনি একটি free preview lesson দেখছেন।
এখন পর্যন্ত আমরা দেখেছি Spring কীভাবে application components manage করে।
আমাদের application flow:
Handler
↓
UseCase
↓
Repository
External dependency থাকলে:
Handler
↓
UseCase
├── Repository
└── External Service
Spring ApplicationContext এই components create এবং wire করতে পারে।
কিন্তু real backend application-এ শুধু objects connect করলেই হয় না।
Application-এর বিভিন্ন runtime value প্রয়োজন হয়।
For example:
database URL
database username
database password
payment provider URL
payment timeout
application port
logging settings
Question:
এই values কোথায় থাকবে?
Hardcoded Java code-এর মধ্যে?
application.yml-এ?
Environment variable-এ?
Spring Bean-এর constructor-এ?
এই lesson-এ আমরা সেই boundary পরিষ্কার করব।
What Is Application Configuration?
Application configuration হলো এমন values যা application-এর behaviour বা infrastructure setup influence করে, কিন্তু সাধারণত business logic-এর source code নয়।
For example:
Payment provider base URL
Production-এ হতে পারে:
https://payments.example.com
Local development-এ:
http://localhost:9001
PayOrderUseCase-এর business responsibility কিন্তু একই থাকে।
So the URL is configuration।
Code vs Configuration
A useful distinction:
Code
Defines:
what the application does
Example:
order.markPaid();
Configuration
Defines:
how this deployment should connect or behave
Example:
payment provider URL
database credentials
timeout
Code should remain stable across environments where possible।
Configuration changes with deployment context।
The Problem With Hardcoding
Suppose:
public class ProviderPaymentService {
private static final String BASE_URL =
"https://payments.example.com";
}
This creates several problems।
Local development may need another URL।
Tests may need a fake server।
Staging may use a sandbox endpoint।
Production may use the real endpoint।
If every environment requires source-code changes:
configuration
has leaked into
implementation
Worse: Hardcoded Secrets
Never do:
private static final String API_KEY =
"secret-production-key";
inside source code।
If committed to Git, the secret may survive in:
Git history
forks
CI logs
developer machines
even after the visible line is removed।
Secrets require external configuration management।
Configuration Should Enter at the Edge
A healthy flow:
Runtime Environment
↓
Spring Configuration
↓
Infrastructure Component
↓
UseCase
For Payment:
PAYMENT_BASE_URL
PAYMENT_API_KEY
PAYMENT_TIMEOUT
↓
ProviderPaymentService
↓
PayOrderUseCase
PayOrderUseCase does not need to know where configuration came from।
Keep Configuration Out of UseCases
Bad:
public class PayOrderUseCase {
public void execute(...) {
String url =
System.getenv("PAYMENT_URL");
// ...
}
}
Now UseCase knows:
environment variable naming
deployment configuration
external provider setup
Its business responsibility is polluted।
Better:
public class PayOrderUseCase {
private final PaymentService paymentService;
public PayOrderUseCase(
PaymentService paymentService
) {
this.paymentService = paymentService;
}
}
The configured external Service handles provider communication।
Spring Boot Configuration Sources
Spring Boot can read configuration from multiple sources।
Common examples:
application.yml
application.properties
environment variables
command-line arguments
system properties
We do not need to memorize every precedence rule right now।
The important idea:
Spring Boot provides one configuration model that can receive values from different runtime sources.
Our Initial application.yml
Current project may have:
spring:
application:
name: order-management
This is safe baseline configuration।
As features arrive, we may add more settings।
Example:
payment:
base-url: http://localhost:9001
timeout: 2s
But credentials should normally not be committed as actual secrets।
Configuration Keys Should Express Meaning
Good:
payment:
base-url: ...
timeout: ...
Weak:
config:
value1: ...
value2: ...
Configuration is part of the application contract।
Names should tell engineers what they control।
Environment Variables
A deployment environment might provide:
PAYMENT_BASE_URL
and Spring can map environment-style names to application properties where appropriately configured/named।
For example, an application property conceptually named:
payment.base-url
can be supplied externally rather than hardcoded।
This allows:
same application artifact
+
different runtime configuration
Why the Same Artifact Matters
Suppose we build one application JAR:
order-management.jar
Ideally we should be able to deploy that same artifact to:
local
test
staging
production
with different configuration।
Bad model:
production branch
staging branch
local source changes
Better:
same code
same artifact
different configuration
This is easier to reason about and release safely।
@Value
Spring provides a direct way to inject individual properties।
Example:
@Component
public class ProviderPaymentService {
private final String baseUrl;
public ProviderPaymentService(
@Value("${payment.base-url}")
String baseUrl
) {
this.baseUrl = baseUrl;
}
}
This works।
But as configuration grows, many individual @Value injections can become noisy।
The Problem With Too Many @Values
Imagine:
public ProviderPaymentService(
@Value("${payment.base-url}")
String baseUrl,
@Value("${payment.api-key}")
String apiKey,
@Value("${payment.timeout}")
Duration timeout,
@Value("${payment.max-response-size}")
DataSize maxResponseSize
) {
}
Technically valid।
But configuration belonging to one capability is spread across string expressions।
A typed configuration object is often clearer।
Typed Configuration Properties
Spring Boot supports binding groups of configuration into a dedicated type।
Conceptually:
@ConfigurationProperties(
prefix = "payment"
)
public record PaymentProperties(
String baseUrl,
Duration timeout
) {
}
Configuration:
payment:
base-url: http://localhost:9001
timeout: 2s
Spring binds:
payment.base-url
↓
baseUrl
payment.timeout
↓
timeout
Now infrastructure code receives one meaningful configuration object।
Why Typed Configuration Is Useful
Instead of:
random strings
we have:
PaymentProperties
which communicates:
These values configure our payment integration.
Benefits:
type safety
discoverability
grouping
validation
easier testing
Configuration Properties Are Not Business Domain Objects
PaymentProperties represents runtime infrastructure configuration।
It is not a domain entity।
Don't put it in:
payment/domain/
A more natural place could be near:
payment/integration/
or payment configuration code।
Ownership matters।
Keep Configuration Near the Capability
Suppose Payment integration has:
PaymentProperties
PaymentConfiguration
ProviderPaymentService
Keeping them together makes the integration easier to understand।
Avoid one giant root:
config/
containing unrelated:
PaymentProperties
DatabaseProperties
SecurityProperties
EmailProperties
unless there is a genuine application-wide reason।
Configuration Example
Conceptually:
@ConfigurationProperties(
prefix = "payment"
)
public record PaymentProperties(
String baseUrl,
Duration timeout
) {
}
Then:
@Configuration
public class PaymentConfiguration {
@Bean
public PaymentService paymentService(
PaymentProperties properties
) {
return new ProviderPaymentService(
properties.baseUrl(),
properties.timeout()
);
}
}
Flow:
Runtime configuration
↓
PaymentProperties
↓
PaymentConfiguration
↓
ProviderPaymentService
↓
PayOrderUseCase
This keeps the UseCase clean।
Configuration Binding Is Infrastructure Work
PayOrderUseCase should care about:
payment success
payment failure
order eligibility
It should not care about:
property prefixes
environment variables
YAML structure
Those belong to infrastructure/configuration boundaries।
Database Configuration
Later PostgreSQL setup may use standard Spring properties such as:
spring:
datasource:
url: jdbc:postgresql://localhost:5432/order_management
username: order_management
Password should not be committed as a production secret।
Runtime could supply it externally।
The important architecture boundary:
database configuration
↓
Spring database infrastructure
↓
Repository
↓
UseCase
UseCase never reads database credentials।
Good Configuration Boundary
CreateOrderUseCase sees:
private final OrderRepository orderRepository;
Not:
private final String databaseUrl;
private final String databaseUsername;
private final String databasePassword;
UseCase depends on persistence capability, not persistence setup।
Configuration Defaults
Sometimes a property can have a safe default।
For example:
payment:
timeout: 2s
might be a reasonable application-owned default if agreed by the engineering team।
But defaults should be deliberate।
Bad default:
missing payment URL
→ silently use production
That can be dangerous।
Required Configuration Should Be Required
If application cannot function without:
PAYMENT_BASE_URL
then absence should ideally cause clear startup/configuration failure once the Payment capability is required।
Don't hide missing required configuration with arbitrary values।
Fail Fast on Invalid Configuration
Suppose:
payment.timeout = -5s
That does not make sense।
Better to detect invalid configuration during application startup than during the first payment request।
This follows the same principle we used for dependency wiring:
Invalid required application setup should fail early.
Configuration Validation
Typed configuration can be validated।
Conceptually:
@ConfigurationProperties(
prefix = "payment"
)
public record PaymentProperties(
String baseUrl,
Duration timeout
) {
}
could later enforce requirements such as:
base URL required
timeout positive
Exact validation annotations/mechanics can be introduced when the properties become real।
The important principle is that configuration has its own validity rules।
Don't Invent Configuration Before the Feature Exists
Our application does not yet implement Payment。
Therefore don't add:
payment:
base-url: ...
api-key: ...
retry-count: ...
timeout: ...
to the current project just because Payment appears later in the roadmap।
Configuration should arrive with the capability that uses it।
Same Rule for Database
BACKEND-102 introduces PostgreSQL and Flyway।
That's when database configuration becomes real।
Do not fill the bootstrap project with unused infrastructure settings।
Configuration Belongs to Current Requirements
This continues our earlier rule:
Dependency arrives
when requirement needs it.
Configuration arrives
when component needs it.
Avoid speculative setup।
Secrets vs Normal Configuration
Not all configuration is secret।
Examples of normal configuration:
application name
timeout
page-size default
provider base URL
Potential secrets:
database password
payment API key
client secret
Treat these categories differently।
Never Log Secrets
Suppose application startup logs:
Payment configuration:
apiKey=abc123...
Now secret leaks into centralized logging।
Even if configuration is externalized correctly, careless logging can expose it।
Avoid printing:
passwords
tokens
API keys
client secrets
Don't Put Secrets in Error Messages
Bad:
Failed connecting with key abc123...
Operational errors should give enough context to debug without exposing credentials।
Environment Variables Are Not Automatically Secure
Moving:
API_KEY
from source code to environment variable is better than committing it to Git।
But secrets still require careful operational handling।
They may be visible to:
process/container configuration
deployment systems
authorized operators
Later production-readiness lessons will discuss secrets management more deeply।
Configuration Should Be Typed Where Useful
Bad:
String timeout;
then manually parse:
Integer.parseInt(timeout);
Spring Boot can bind configuration to meaningful Java types such as:
Duration
boolean
integer
Typed configuration reduces parsing scattered across infrastructure code।
Example: Duration
Configuration:
payment:
timeout: 2s
Java:
Duration timeout
This is clearer than:
2000
where nobody knows whether the unit is:
milliseconds?
seconds?
Use configuration formats that communicate meaning।
Don't Pass Configuration Through Every Layer
Bad:
Handler
↓ passes payment URL
UseCase
↓ passes payment URL
Payment Service
The Handler and UseCase don't need that information।
Better:
Payment configuration
↓
Payment Service
Handler
↓
UseCase
↓
Payment Service
Only the component that needs configuration receives it।
Configuration Is a Dependency Too
A third-party Service implementation can depend on configuration।
Example:
public class ProviderPaymentService {
private final PaymentProperties properties;
public ProviderPaymentService(
PaymentProperties properties
) {
this.properties = properties;
}
}
This is still Dependency Injection।
Configuration objects can be dependencies of infrastructure components।
But Don't Inject Configuration Everywhere
Bad:
public CreateOrderUseCase(
OrderRepository orderRepository,
ApplicationProperties properties
) {
}
if UseCase only needs one unrelated infrastructure setting indirectly।
Prefer giving the appropriate dependency to the component that actually needs it।
Avoid One Giant ApplicationProperties
Example:
public class ApplicationProperties {
String databaseUrl;
String paymentApiKey;
String identityUrl;
String emailApiKey;
int pageSize;
Duration timeout;
...
}
Then every component receives the giant object।
This creates broad coupling।
Better:
PaymentProperties
IdentityProperties
feature-specific configuration
where appropriate।
Configuration Ownership
Ask:
Which component/capability needs this property?
Example:
payment.timeout
belongs to Payment integration।
order.history.default-page-size
might belong to Order API/query behaviour if later needed।
spring.datasource.url
belongs to persistence infrastructure।
Grouping by responsibility keeps configuration understandable।
application.yml Is Not a Database
Do not store changing business data in configuration।
Bad:
products:
- id: 1
name: Laptop
price: 1000
for production Product catalog।
Product data belongs in application persistence।
Configuration is for application/runtime setup, not business records।
Configuration vs Feature State
A useful distinction:
Configuration
→ deployment/runtime behaviour
Business state
→ Product, Order, Inventory, Payment data
Don't blur them।
Configuration Changes Can Be Risky
Changing:
payment timeout
may affect production behaviour even without code changes।
Therefore configuration changes should be reviewed and managed deliberately।
"Not code" does not mean "not engineering."
Configuration Should Be Documented
If a developer needs to run the application, required configuration should be discoverable।
For example README may eventually describe:
required environment variables
local defaults
how to start PostgreSQL
Do not rely on tribal knowledge like:
Ask Sakib for the secret variable names.
A reproducible project documents its runtime requirements।
Local Development Configuration
Later we may need local values like:
local PostgreSQL URL
fake provider URL
The goal is to make local setup convenient without committing production secrets।
We will handle environment-specific files in the next lesson।
For now:
Separate application configuration from business logic first.
Command-Line Overrides
Spring Boot can accept command-line property overrides.
Conceptually:
java -jar app.jar \
--payment.timeout=3s
This can be useful operationally।
But don't design normal deployments around long undocumented command lines।
Runtime configuration should remain intentional and manageable।
Configuration Precedence
Spring Boot supports multiple configuration sources and precedence rules।
We do not need the full ordering table in this course lesson।
What matters now:
application can provide defaults
+
environment/deployment can override
appropriate values
When exact precedence becomes operationally relevant, check Spring Boot documentation for the project version।
Don't Depend on Accidental Precedence
If two sources define conflicting values and nobody understands which wins, deployment becomes fragile।
Prefer a clear convention:
safe application defaults
explicit environment-specific overrides
external secrets
and document it।
Example: Payment Configuration Boundary
Let's put everything together conceptually.
Configuration:
payment:
base-url: http://localhost:9001
timeout: 2s
Properties:
public record PaymentProperties(
String baseUrl,
Duration timeout
) {
}
External Service:
public class ProviderPaymentService
implements PaymentService {
private final String baseUrl;
private final Duration timeout;
public ProviderPaymentService(
String baseUrl,
Duration timeout
) {
this.baseUrl = baseUrl;
this.timeout = timeout;
}
@Override
public PaymentResult pay(
PaymentRequest request
) {
return null;
}
}
UseCase:
public class PayOrderUseCase {
private final OrderRepository orderRepository;
private final PaymentService paymentService;
public PayOrderUseCase(
OrderRepository orderRepository,
PaymentService paymentService
) {
this.orderRepository = orderRepository;
this.paymentService = paymentService;
}
}
Notice:
PayOrderUseCase
knows nothing about:
base URL
timeout property names
environment variables
That's the boundary we want।
Configuration and Testing
Suppose ProviderPaymentService receives:
PaymentProperties
A focused test can provide explicit test configuration:
PaymentProperties properties =
new PaymentProperties(
"http://localhost:9001",
Duration.ofSeconds(1)
);
No need to mutate global environment state in every test।
Typed configuration improves testability too।
Testing UseCases Does Not Need Configuration
PayOrderUseCase test can simply inject a fake:
PaymentService paymentService =
new SuccessfulPaymentService();
The UseCase does not care about provider URL at all।
This is another benefit of keeping configuration at the integration boundary।
Configuration and the Application Context
The complete flow:
application.yml / environment
↓
Spring configuration system
↓
Configuration Properties
↓
Bean construction
↓
Repository / External Service
↓
UseCase
↓
Handler
The Application Context connects configured components to application workflows।
Avoid Configuration in Domain Objects
Bad:
public class Order {
private final Environment environment;
}
or:
public class Inventory {
private final PaymentProperties properties;
}
Domain objects should not know deployment configuration unless a value genuinely represents a business rule passed explicitly into the domain operation।
Even then, prefer business concepts rather than Spring configuration types।
Business Rule vs Configurable Setting
Suppose future requirement says:
Customers may order at most 10 units of a product.
Question:
Should we immediately implement:
order:
max-product-quantity: 10
Not necessarily।
If 10 is a fixed business rule, making it runtime configurable may introduce complexity and accidental behaviour changes।
Not every number should be configuration।
When Should a Business Value Be Configurable?
Ask:
Does operations need to change this independently of deployment?
Does it vary by environment?
Is it truly runtime configuration,
or part of product behaviour?
Do not turn business rules into knobs without a reason।
Configuration Is Not a Substitute for Product Design
Bad:
Nobody knows cancellation eligibility.
Let's put cancellation-days in application.yml.
That avoids the product decision rather than solving it।
First establish the business rule।
Then decide whether runtime configurability is actually needed।
Configuration Smell: Too Many Knobs
An application with hundreds of rarely understood settings can become harder to operate than code with sensible decisions।
Configuration has complexity cost।
Prefer:
small
meaningful
well-owned
documented
configuration surface।
Configuration Smell: Generic Names
Avoid:
timeout=1000
url=...
enabled=true
at the application root।
Prefer qualified ownership:
payment.timeout
payment.base-url
This becomes increasingly important as the application grows।
Configuration Smell: Optional Everything
If every property has a fallback:
missing URL → fallback
missing password → empty string
missing timeout → zero
application may start in an invalid state।
Required configuration should remain required।
Configuration Smell: Business Logic Reading Environment
Avoid:
System.getenv(...)
throughout Handlers and UseCases।
Centralize runtime configuration through Spring's configuration model and relevant infrastructure Beans।
Configuration Smell: Secret in YAML
Do not commit:
payment:
api-key: actual-production-secret
to the repository।
Use an external secret source appropriate to the deployment environment।
Configuration Smell: One Properties Object Everywhere
Avoid passing:
ApplicationProperties
into ten unrelated components।
Keep dependencies focused।
Configuration Review Checklist
Before adding a configuration property, ask:
Why does this value need to vary?
Who owns it?
Is it infrastructure configuration or business state?
Is it secret?
Does it need a safe default?
Should missing value fail startup?
Which component actually needs it?
Can it use a meaningful Java type?
Is the property name clear?
Do we need this configuration now?
Our Current Project
At this point in BACKEND-101, our actual configuration may still remain simple:
spring:
application:
name: order-management
That's okay।
This lesson prepares us for later tickets।
We should not fill the project with fictional configuration just because we understand the mechanism।
How Configuration Will Appear Later
BACKEND-102:
PostgreSQL connection configuration
Payment module:
Payment Service endpoint
credentials
timeout
Security module:
identity/security integration settings
Production readiness:
operational configuration
Each setting arrives with a real capability।
Engineering Principle
The core principle:
Runtime configuration should enter the system through the application's composition and infrastructure boundaries, not leak into Handlers, UseCases, or domain objects.
Another:
Build one application artifact and vary environment-specific behaviour through controlled configuration rather than source-code changes.
And:
Configuration is part of system design—keep it small, typed, owned, and explicit.
Summary
In this lesson, we learned that:
- Application configuration contains runtime/deployment values rather than core business logic.
- Hardcoding environment-specific values creates unnecessary coupling.
- Secrets must not be committed into source code.
- Spring Boot can read configuration from files, environment variables, command-line arguments, and other sources.
application.ymlcan contain safe application defaults.- The same built application should ideally run across environments with different configuration.
- Handlers and UseCases should not read environment variables directly.
- External Service implementations and infrastructure components should receive the configuration they need.
@Valuecan inject individual properties.- Typed
@ConfigurationPropertiesbecomes clearer when several related properties belong together. - Configuration should stay close to the capability that owns it.
- Database credentials belong to database infrastructure, not UseCases.
- Payment configuration belongs to the Payment integration boundary.
- Required invalid configuration should fail early where practical.
- Defaults should be intentional rather than hiding missing setup.
- Configuration and secrets are different concerns.
- Environment variables improve externalization but are not automatically a complete secrets-management solution.
- Sensitive values should never be logged.
- Use meaningful Java types such as
Durationinstead of ambiguous raw values where appropriate. - Configuration should not be passed through layers that do not need it.
- One giant
ApplicationPropertiesobject creates broad coupling. - Business data such as Products, Orders, and Inventory does not belong in configuration.
- Not every business rule should become a runtime-configurable property.
- Configuration has complexity cost and should remain purposeful.
- Configuration should be documented for local development and deployment.
- Configuration should be introduced when the corresponding feature actually exists.
Next lesson:
Environment-Specific Configuration
There we will build on this foundation and understand how local, test, non-production, and production environments can use different configuration without branching the codebase or committing secrets.