Back to Blog
2026-03-29
8 min read

Most Java developers can launch a backend with one annotation. Fewer can explain what happens after that annotation disappears.

#Java#Spring Core#Dependency Injection#Backend Architecture#JDBC#HikariCP#System Design

Deconstructing Spring Boot: Building a Billing Backend with Pure Spring Core

Why I Removed the "Magic"

Spring Boot is excellent for shipping fast. But I wanted to strengthen my backend fundamentals by understanding what Boot normally hides: container bootstrapping, bean wiring, property source handling, and infrastructure setup.

So I built a billing and notification workflow using Spring Core 6.1.4 only. No @SpringBootApplication, no starters, and no auto-configuration.

What I Built

This project demonstrates a production-minded architecture in a small, focused codebase:

  1. Manual IoC container bootstrapping with AnnotationConfigApplicationContext
  2. Explicit Java-based configuration for component scanning and property sources
  3. Custom YAML loader to make @PropertySource read application.yml
  4. Manual HikariCP DataSource wiring and JdbcTemplate setup
  5. Strategy-style payment routing using PaymentProcessor interface + qualifiers
  6. Runtime notification channel swap via setter injection (EmailSender to SmsSender)
  7. Schema initialization on startup using @PostConstruct

Architecture Walkthrough

  • Bootstrap layer: Creates the container and executes billing flows
  • Config layer: Registers infrastructure beans (DataSource, JdbcTemplate), loads YAML
  • Domain service layer: Billing orchestration + notifications
  • Processor layer: Pluggable payment and notification implementations

The design goal was simple: keep the business logic independent of concrete providers.

Key Engineering Decisions

  • Constructor injection for mandatory dependencies in billing logic
  • Setter injection for runtime behavior change in notification strategy
  • @Qualifier for deterministic bean resolution when multiple implementations exist
  • JdbcTemplate for concise, safe SQL execution without raw JDBC boilerplate

Example Flow

  1. Container starts with Java config
  2. Schema is created if missing
  3. Billing service processes invoice through selected payment processor
  4. Successful transaction is persisted to DB
  5. Notification service sends receipt
  6. Notification sender is swapped at runtime and second billing run uses new channel

What This Project Is (and Isn’t)

This is a foundational backend architecture project that proves strong understanding of Spring internals and DI mechanics.

It is not presented as a full enterprise product yet (no auth, no distributed tracing, no retry/idempotency pipeline), but it is a solid base to evolve toward that.

What I Learned

  • Spring Core gives precise control when you need predictability
  • Boot convenience is more valuable after understanding manual wiring
  • Interfaces + qualifiers make provider swaps cheap and safe
  • Infrastructure ownership (pooling, properties, schema) improves debugging confidence

Next Up

I plan to extend this with transaction boundaries, integration tests, idempotent payment handling, and structured observability to move from architecture demo to production-grade service.