Building the Spring Boot Application

Configuration and Application Properties

ReadingPreview

আপনি একটি 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.yml can 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.
  • @Value can inject individual properties.
  • Typed @ConfigurationProperties becomes 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 Duration instead of ambiguous raw values where appropriate.
  • Configuration should not be passed through layers that do not need it.
  • One giant ApplicationProperties object 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.