Building the Spring Boot Application

Creating a Spring Boot Application

LuettavaEsikatselu

Katselet ilmaista esikatseluoppituntia.

Until now, we have intentionally avoided writing production code.

We first worked through:

Product Requirement
        ↓
Acceptance Criteria
        ↓
Engineering Context
        ↓
Backlog
        ↓
Technical Design
        ↓
RFC
        ↓
ADR
        ↓
Design Review

The design is now sufficiently clear.

It is time to begin implementation.

Our first ticket is:

BACKEND-101
Bootstrap Order Management Backend

In this lesson, we will create a minimal but production-oriented Spring Boot application.

The goal is not to create as many folders, dependencies, or abstractions as possible.

The goal is:

Create a clean, reproducible Java application foundation on which the entire Order Management Backend can evolve.


Ticket: BACKEND-101

Our initial backlog defined the ticket as:

# BACKEND-101 — Bootstrap Order Management Backend

## Context

We need the initial Spring Boot application
for backend implementation.

## Scope

- Create the Gradle project.
- Configure Spring Boot.
- Establish the application entry point.
- Add baseline project configuration.
- Verify the application can build and start locally.

## Acceptance Criteria

- The project builds successfully with Gradle.
- The Spring Boot application starts successfully.
- The repository contains the agreed baseline project structure.
- A clean checkout can be built without IDE-specific setup.

## Out of Scope

- Product APIs
- Database schema
- Authentication
- Order logic
- Payment integration
- Production Docker setup

This scope will guide our implementation.


Start With the Ticket, Not Spring Initializr

Creating a Spring Boot project technically takes only a few minutes.

But a backend engineer's first question should not be:

Which checkboxes should I select?

The better question is:

What is the minimum required to complete this ticket correctly?

Right now, we need:

Java project

Gradle build

Spring Boot runtime

application entry point

basic configuration

repeatable build

We do not currently need:

PostgreSQL

Spring Data JPA

Flyway

Spring Security

WebClient

Testcontainers

Payment SDK

Those will arrive when later tickets actually require them.


Avoid Dependency Shopping

A common mistake when creating a Spring Boot project is immediately adding:

Spring Web

Spring Data JPA

PostgreSQL

Spring Security

Redis

Kafka

Actuator

Validation

WebFlux

Lombok

because:

We will probably need them eventually.

That creates:

larger dependency surface

more configuration

more startup behaviour

more things to understand

more accidental architecture

Our principle is:

Add a dependency when a current engineering requirement needs it.

The fact that a dependency may become useful later is not enough reason to install it now.


Initial Technology Choices

Our engineering context already established:

Language
→ Java

Framework
→ Spring Boot

Build Tool
→ Gradle

These are existing project constraints.

We do not need another technology-comparison exercise before implementation.

Our job is to create a good foundation using those choices.


Repository Name

Our project repository is:

order-management

Initially:

order-management/
├── build.gradle
├── settings.gradle
├── gradlew
├── gradlew.bat
├── gradle/
└── src/

Later, the repository may evolve toward:

order-management/
├── src/
├── docs/
│   ├── rfcs/
│   ├── adr/
│   └── incidents/
├── compose.yaml
├── Dockerfile
└── README.md

But we should not create every future file now.

Architecture should grow with real implementation needs.


Why Use the Gradle Wrapper?

A Gradle project should normally be built using:

./gradlew build

instead of requiring every developer to install and manually manage a matching Gradle version.

The Gradle Wrapper includes:

gradlew

gradlew.bat

gradle/wrapper/

These files are committed with the project.

That means:

developer machine

CI environment

another engineer's machine

can all use the project-defined Gradle distribution.

This directly supports our Acceptance Criterion:

A clean checkout can be built without IDE-specific setup.


Build Reproducibility Matters

Weak project setup:

It works from IntelliJ
on my machine.

Stronger project setup:

git clone
./gradlew build

works from a supported Java environment.

An IDE is a useful development tool.

It is not the source of truth for the build.

The repository and Gradle build definition are.


Initial Gradle Settings

A minimal settings.gradle:

rootProject.name = 'order-management'

Its responsibility is simple:

define the Gradle project name

We do not need application architecture configuration here.


Initial build.gradle

A minimal Spring Boot build can look conceptually like this:

plugins {
    id 'java'
    id 'org.springframework.boot' version '<spring-boot-version>'
    id 'io.spring.dependency-management' version '<dependency-management-version>'
}

group = 'io.tarkev'
version = '0.0.1-SNAPSHOT'

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(21)
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter'

    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

tasks.named('test') {
    useJUnitPlatform()
}

The version placeholders are intentional in this lesson.

When the actual project is bootstrapped, the team-supported versions are pinned in the build.

The important thing here is understanding the structure and responsibility of the build.


Why Java Toolchains?

This section:

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(21)
    }
}

declares the Java baseline expected by the project.

Benefits include:

developer consistency

CI consistency

explicit project requirements

Without an explicit baseline, the build may accidentally depend on whichever Java version happens to be installed locally.


Why Not “Whatever Java Is Installed”?

Imagine:

Engineer A
→ Java 21

Engineer B
→ another Java version

CI
→ another version

Subtle differences may appear in:

compilation

language features

runtime behaviour

tooling

A production project should make its supported Java baseline explicit.


Why Only spring-boot-starter?

At this point, the ticket requires only:

a Spring Boot application
that can start successfully

So:

implementation 'org.springframework.boot:spring-boot-starter'

is sufficient for our current foundation.

We are not exposing REST endpoints yet.

Later:

REST/API work
→ Spring Web
Persistence work
→ Spring Data JPA
→ PostgreSQL driver
→ Flyway
Security work
→ Spring Security

Dependencies should arrive with a reason.


Test Dependency

We include:

testImplementation 'org.springframework.boot:spring-boot-starter-test'

because testing is already part of our Definition of Done.

Testing does not suddenly begin when we reach the dedicated Testing module.

The later module will deepen our testing strategy.

But every engineering change should already have testing appropriate to the behaviour it introduces.


Standard Source Structure

Our project follows the standard Java structure:

src/
├── main/
│   ├── java/
│   └── resources/
└── test/
    └── java/

Meaning:

src/main/java
→ application source code
src/main/resources
→ application resources
→ configuration
src/test/java
→ automated tests

Spring Boot works naturally with this structure.


Base Package

Our Tarkev namespace is:

io.tarkev.ordermanagement

So application source lives under:

src/main/java/io/tarkev/ordermanagement/

Java package names provide:

namespace

code organization

component scanning boundary

As the application grows, capabilities may appear below it:

io.tarkev.ordermanagement.product

io.tarkev.ordermanagement.inventory

io.tarkev.ordermanagement.order

io.tarkev.ordermanagement.payment

io.tarkev.ordermanagement.security

We do not need to create those packages yet.


Why the Main Class Belongs at the Root

Our application entry point lives at:

io.tarkev.ordermanagement.OrderManagementApplication

This placement works naturally with Spring Boot's default component scanning.

Conceptually:

io.tarkev.ordermanagement
├── OrderManagementApplication
├── product
├── inventory
└── order

Spring can discover application components in subpackages beneath the root package.

That makes the root a natural home for the main application class.


Application Entry Point

Create:

package io.tarkev.ordermanagement;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class OrderManagementApplication {

    public static void main(String[] args) {
        SpringApplication.run(
                OrderManagementApplication.class,
                args
        );
    }
}

This is our application entry point.


What Does main Do?

A normal Java application begins with:

public static void main(String[] args)

Spring Boot does not change that Java rule.

The JVM still starts from main.

Inside it:

SpringApplication.run(...)

bootstraps Spring.

Conceptually:

JVM starts
    ↓
main(...)
    ↓
SpringApplication.run(...)
    ↓
Spring Application Context created
    ↓
application starts

We will explore the Application Context in more detail later.


@SpringBootApplication

The annotation:

@SpringBootApplication

marks our main Spring Boot application configuration.

For now, think of it as the application bootstrap annotation.

It enables Spring Boot's core startup conventions, including configuration and component discovery behaviour.

We will later examine what this annotation actually combines and how Spring Beans and the Application Context work.


Don't Memorize the Annotation Yet

A beginner may see:

@SpringBootApplication

and think:

I need to memorize this annotation.

A better question is:

What responsibility does it fulfill?

At this stage:

application bootstrap
+
primary configuration entry point

Understanding responsibility is more useful than memorizing annotation names.


Application Configuration

Inside:

src/main/resources/

create:

application.yml

The initial configuration can remain very small:

spring:
  application:
    name: order-management

That gives the application an explicit name.

We do not add future database, Payment, or security configuration yet.


Why YAML?

Spring Boot supports several configuration formats.

For this project, we use:

application.yml

for structured configuration.

This is a project convention rather than an important architectural decision.

The important thing is consistency.

We do not need an architecture debate about YAML versus .properties.


Never Commit Secrets Into Configuration

Even in the bootstrap phase, establish the correct habit.

Bad:

payment:
  api-key: super-secret-key

committed to Git.

Later credentials should come from appropriate external configuration or environment mechanisms.

We will design production configuration more fully in the production-readiness module.


Initial Project Structure

After BACKEND-101, our project can look like:

order-management/
├── build.gradle
├── settings.gradle
├── gradlew
├── gradlew.bat
├── gradle/
│   └── wrapper/
│
└── src/
    ├── main/
    │   ├── java/
    │   │   └── io/
    │   │       └── tarkev/
    │   │           └── ordermanagement/
    │   │               └── OrderManagementApplication.java
    │   │
    │   └── resources/
    │       └── application.yml
    │
    └── test/
        └── java/
            └── io/
                └── tarkev/
                    └── ordermanagement/

That is enough for the initial foundation.


Where Are the Product and Order Packages?

They do not exist yet.

Why?

Because:

BACKEND-101

does not implement Product or Order.

Our architecture says those capabilities will exist.

That does not mean we need empty folders proving that we once drew them in a diagram.

We will create the packages when implementation introduces the corresponding responsibilities.


Avoid Architecture by Empty Folder

A poor bootstrap might create:

product/
    handler/
    application/
    repository/
    domain/
    dto/
    mapper/

inventory/
    handler/
    application/
    repository/
    domain/

order/
    handler/
    application/
    repository/
    domain/
    dto/
    mapper/

while every directory remains empty.

That is not meaningful architecture.

It is speculative folder generation.

Good architecture becomes visible as real responsibilities arrive.


First Build

Run:

./gradlew build

Conceptually, Gradle:

reads build.gradle
        ↓
resolves dependencies
        ↓
compiles main source
        ↓
compiles test source
        ↓
runs tests
        ↓
packages the application

A successful build ends with something equivalent to:

BUILD SUCCESSFUL

That satisfies an important part of BACKEND-101.


Why Build Before Running?

Starting the application directly from an IDE can hide repository-level build problems.

Running:

./gradlew build

verifies:

project definition

dependency resolution

compilation

tests

packaging

We want the repository build to be healthy before feature work begins.


Running the Application

Run:

./gradlew bootRun

Spring Boot starts the application.

At this stage, we do not necessarily have an HTTP server.

Why?

Because we deliberately have not added Spring Web.

That is fine.

Our Acceptance Criterion says:

Spring Boot application starts successfully.

It does not say:

GET /api/v1/products works.

Product APIs belong to later work.


Running the Built Artifact

After:

./gradlew build

Spring Boot produces an executable application artifact under:

build/libs/

It can also be run outside the IDE:

java -jar build/libs/<application-jar>.jar

This is useful because it demonstrates that the application is genuinely runnable from its build artifact.


The IDE Is Optional to the Build

Developers may use:

IntelliJ IDEA

VS Code

another Java IDE

But the engineering model remains:

Repository
+
Gradle
=
authoritative build

not:

IDE-specific configuration
=
authoritative build

This becomes especially important when CI is introduced.


First Test

A minimal Spring Boot bootstrap test can look like:

package io.tarkev.ordermanagement;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class OrderManagementApplicationTests {

    @Test
    void contextLoads() {
    }
}

The test appears almost empty.

Its purpose is specific:

Can Spring successfully create the application context?


What Does contextLoads() Protect?

Suppose later startup configuration becomes invalid because of:

missing required Bean

broken dependency wiring

invalid Spring configuration

An application-context test can catch some classes of bootstrap failure.

For BACKEND-101, that is reasonable test coverage.


Don't Overvalue contextLoads()

A passing:

contextLoads()

does not prove:

business logic is correct

database behaviour is correct

REST API is correct

authorization is correct

It provides startup-level confidence.

Testing should always correspond to the behaviour being protected.


Testing Starts With the Work

For BACKEND-101, relevant behaviours are:

project compiles

Spring context starts

Gradle build succeeds

So a bootstrap test is appropriate.

Later:

Product behaviour
→ Product tests
Order lifecycle
→ Order tests
PostgreSQL behaviour
→ integration tests

We do not create fake business tests before those behaviours exist.


Build Output

After building:

build/

contains generated output.

That directory should not be committed to Git.

Why?

Because it is reproducible build output.

The repository should primarily contain:

source

build definition

configuration

migrations

documentation

rather than generated artifacts.


.gitignore

A basic .gitignore may contain:

.gradle/
build/

.idea/
*.iml

.vscode/

.DS_Store

Exact IDE rules depend on team conventions.

The important principle is:

Do not commit machine-generated local state unless the repository intentionally owns it.


The Gradle Wrapper Is Different

Do not ignore:

gradlew

gradlew.bat

gradle/wrapper/

These files are intentionally versioned.

They are part of the project's reproducible build tooling.

This is different from generated build output.

Think of it as:

build/
→ generated output
→ ignore

versus:

gradlew + wrapper
→ versioned build tooling
→ commit

README at Bootstrap Stage

A lightweight README.md helps another engineer start the project.

For example:

# Order Management Backend

Backend application for the Commerce Order Management project.

## Requirements

- Supported Java version

## Build

```bash
./gradlew build
```

## Run

```bash
./gradlew bootRun
```

The README should describe what works now.

Do not fill it with fictional future commands.


Clean Checkout Test

One Acceptance Criterion says:

A clean checkout can be built without IDE-specific setup.

Conceptually:

fresh checkout
        ↓
supported Java available
        ↓
./gradlew build

should succeed.

This is one of the best tests of project-bootstrap quality.


Hidden Local Dependencies

Imagine the build works only because your machine has:

a manually installed library

IDE-generated source

custom local configuration

uncommitted files

unexplained environment state

Then the project is not reproducible.

BACKEND-101 exists partly to prevent this problem before the codebase becomes larger.


Should We Add PostgreSQL Now?

No.

That belongs to:

BACKEND-102
Configure PostgreSQL and Flyway

Adding PostgreSQL here would:

  • expand BACKEND-101 scope
  • make first startup depend on database infrastructure
  • combine two separate engineering concerns

We deliberately keep tickets bounded.


Should We Add Spring Web Now?

The application will eventually expose REST APIs.

Could we add Spring Web now?

Technically, yes.

But the current ticket does not need an HTTP boundary.

We will introduce Spring Web when the course reaches the work that actually exposes REST APIs.

Until then, it would be an unused future dependency.


Should We Add Actuator Now?

No.

Actuator becomes useful when health checks and metrics become actual production-readiness requirements.

Adding it now without using or understanding it gives us extra behaviour with no current purpose.

We will add it when that requirement arrives.


Should We Add Lombok?

There is no current requirement for it.

Modern Java already gives us:

constructors

records

normal classes

IDE generation

where needed.

Introducing Lombok would add another compile-time dependency and another concept learners need to understand.

This does not mean Lombok is universally bad.

It simply is not justified by BACKEND-101.


Should We Add MapStruct?

No.

We do not even have meaningful DTO-to-Domain mapping yet.

Do not install a solution before the problem exists.


Should We Add Redis or Kafka?

No.

Neither solves a problem in this ticket.

Our architecture explicitly avoids speculative distributed infrastructure.

We will introduce infrastructure only when a requirement justifies it.


Build Configuration Is Production Engineering

Developers sometimes think:

src/main/java
→ real code

build.gradle
→ setup

That distinction is misleading.

A broken build.gradle can block the entire engineering team.

The build defines:

dependencies

Java version

plugins

test execution

packaging

It is part of the engineering system that produces the application.

Treat build changes carefully.


Dependency Versions Must Be Controlled

Framework and dependency versions should not change simply because:

a newer version exists

An upgrade can affect:

runtime behaviour

security posture

compatibility

build behaviour

dependency resolution

In a real team, meaningful dependency upgrades are explicit engineering changes.

Throughout this course, dependencies evolve because the roadmap introduces a need.


Package Naming Matters, but Don't Overthink It

Our agreed base package is:

io.tarkev.ordermanagement

Consistency matters.

We do not need repeated architecture discussions about:

ordermanagement

orderservice

order_management

once the project convention is established.

Spend design energy on consequential decisions.


Application Name

Our configuration includes:

spring:
  application:
    name: order-management

This is useful because application identity later appears in:

logs

metrics

configuration

deployment

Consistent naming from the beginning reduces unnecessary drift.


0.0.1-SNAPSHOT

The Gradle version:

version = '0.0.1-SNAPSHOT'

is simply an early development artifact version.

We are not designing the final release-versioning strategy in BACKEND-101.

Release/version policy belongs to later delivery work.


What Have We Actually Built?

At this point, almost no business functionality exists.

That is intentional.

We have:

a valid Java project

a Gradle build

the Gradle Wrapper

a Spring Boot runtime

an application entry point

baseline configuration

a bootstrap test

reproducible build/run commands

This is foundation work.

It is not yet feature delivery.


Why This Is Still Meaningful Engineering

Every future backend ticket depends on a healthy application foundation.

If bootstrap is chaotic:

different Java baselines

IDE-only builds

uncontrolled dependencies

random package layout

unreproducible startup

every future feature pays that cost.

BACKEND-101 gives the project a stable starting point.


Definition of Done Review

Now apply our project Definition of Done.

Scope

Did we implement the agreed scope?

[✓] Gradle project exists

[✓] Spring Boot configured

[✓] Application entry point exists

[✓] Baseline configuration exists

[✓] Application builds and starts

Acceptance Criteria

[✓] ./gradlew build succeeds

[✓] Spring Boot application starts

[✓] Baseline project structure exists

[✓] Build does not depend on IDE-specific setup

Testing

Relevant automated test:

[✓] Spring application-context startup test

Existing tests:

[✓] pass

Build

[✓] clean Gradle build succeeds

Scope Discipline

Did we add:

Product API?

Database?

Security?

Payment?

Production Docker setup?

No.

Good.

A feature is not more complete because it includes unrelated future work.


Example Pull Request

A reasonable first Pull Request could be:

## What

Bootstraps the Order Management Backend
as a Spring Boot Gradle application.

## Why

Provides the application foundation required
for subsequent backend capabilities.

## Changes

- Added Gradle project and wrapper
- Added Spring Boot application entry point
- Added baseline application configuration
- Added application-context bootstrap test
- Added basic project documentation

## Testing

- `./gradlew build`
- Application started with `./gradlew bootRun`

## Risks

Low. This introduces the initial application foundation
and no business functionality.

This is a small, focused, reviewable PR.


What Should the Reviewer Check?

Review should focus on the ticket rather than cosmetic details.

Useful questions include:

Does a clean build work?

Is the Java baseline explicit?

Are unnecessary dependencies included?

Is the package root correct?

Can the application start outside the IDE?

Is configuration minimal and safe?

Does the change stay within BACKEND-101 scope?

This keeps review aligned with engineering intent.


Example Review Feedback — Unused PostgreSQL Dependency

Reviewer:

PostgreSQL is included, but this ticket does not configure or use a database. Can we add it in BACKEND-102 instead?

Good feedback.

Response:

Yes.

Remove the unused dependency.

This improves dependency discipline and keeps ticket boundaries clean.


Example Review Feedback — Empty Capability Packages

Reviewer:

There are empty Product, Inventory, Order, and Payment package hierarchies, but nothing uses them.

Also good feedback.

Response:

Remove them.

Create capability structure
when implementation reaches
the corresponding capability.

Our architecture is already captured in:

RFC-001

ADRs

technical design

We do not need empty directories to prove it exists.


Keep the Main Branch Buildable

After BACKEND-101 merges:

./gradlew build

should still succeed on the main branch.

This becomes an ongoing expectation.

Every future change begins from a healthy baseline and should leave one behind.


Small PR, Important Foundation

Our first implementation PR is intentionally small.

That gives us:

easy review

clear scope

simple rollback

clean repository history

Future Git history may tell a coherent story:

BACKEND-101
Bootstrap Order Management Backend

BACKEND-102
Configure PostgreSQL and Flyway

BACKEND-103
Model Product Domain

...

That is easier to understand than one giant initial commit containing half the system.


Spring Boot Is a Tool, Not the Course Goal

Notice how Spring Boot entered this course.

We did not start with:

Today we will learn
@SpringBootApplication.

We started with:

We need to bootstrap
the backend application
we already designed.

Spring Boot solves that engineering need.

This is the mindset we want throughout the course:

Problem first. Technology second.


What We Have Not Learned Yet

We have only touched several Spring concepts:

project structure

dependency injection

Beans

Application Context

configuration

They are upcoming lessons.

We do not need to explain every Spring mechanism before the application can exist.

Learning will follow the evolving system.


Where We Are Now

Module 3 proceeds through:

Creating a Spring Boot Application
        ↓
Understanding Spring Boot Project Structure
        ↓
Dependency Injection
        ↓
Beans and the Application Context
        ↓
Configuration and Application Properties
        ↓
Environment-Specific Configuration
        ↓
Structuring a Backend Codebase
        ↓
Running the Application Locally

BACKEND-101 will evolve through these lessons.

This lesson establishes the executable foundation.


Engineering Principle

The core principle is:

A project bootstrap should establish the minimum reproducible foundation required for the next piece of meaningful work.

Another:

Dependencies, packages, and infrastructure should be added because the current system needs them—not because we expect every backend might eventually use them.

And:

If the project only works through one developer's IDE, the project is not properly bootstrapped.


Summary

In this lesson, we established that:

  • BACKEND-101 is the first implementation ticket.
  • The initial application is a Gradle-based Spring Boot project.
  • The repository build, not the IDE, is the source of truth.
  • Gradle Wrapper makes the project build reproducibly across environments.
  • The Java baseline is declared explicitly through a toolchain.
  • The Tarkev package root is io.tarkev.ordermanagement.
  • The Spring Boot main class provides the application entry point.
  • @SpringBootApplication marks the primary application bootstrap configuration.
  • SpringApplication.run(...) starts the Spring application.
  • Initial configuration should remain minimal.
  • spring.application.name is set to order-management.
  • Secrets must not be committed into application configuration.
  • We do not pre-install PostgreSQL, JPA, Flyway, Security, Redis, Kafka, Actuator, WebFlux, Lombok, MapStruct, or other dependencies before their requirements arrive.
  • Empty package hierarchies do not create meaningful architecture.
  • ./gradlew build must succeed from a clean checkout.
  • ./gradlew bootRun provides a build-system-driven way to start the application.
  • The built executable artifact can also run independently from the IDE.
  • A basic application-context test is appropriate for the bootstrap ticket.
  • Testing begins with the implementation work rather than being postponed.
  • Generated build/ output is ignored, while Gradle Wrapper files are committed.
  • Build configuration is part of production engineering.
  • README documentation should describe what actually works now.
  • BACKEND-101 remains intentionally separate from BACKEND-102 PostgreSQL/Flyway work.
  • The ticket should produce a small, focused, reviewable Pull Request.
  • Reviewers should remove unused dependencies and speculative empty structure.
  • The main branch should remain buildable after the bootstrap change.
  • Spring Boot is being used as a tool for building the system we designed—not as the purpose of the course itself.

Next lesson:

Understanding Spring Boot Project Structure

There, we will examine the project we just created and understand what each part of the repository does, how Java package placement affects Spring Boot behaviour, where configuration, resources, and tests belong, and how to prevent a growing backend from turning into an unstructured collection of files.