RizTech Academy logo
RizTech Academy
Patterns in SpringLesson 4 of 525 min

Do not fight the framework

Spring is opinionated: it has conventions for how things are done, and it does a great deal for you automatically. The single most reliable way to make a Spring codebase painful is to fight those conventions — to work around Spring instead of with it, to re-implement what it provides, to impose a structure it resists. This lesson is a set of concrete "do not fight the framework" principles, because recognising when you are fighting Spring — and stopping — is one of the most valuable instincts a Spring developer can have.

The principle: work with the grain

A framework has a grain — a way it expects things to be done. Work with the grain and everything is easy: the conventions do the heavy lifting, the community's answers apply to your code, upgrades go smoothly. Fight the grain — insist on doing it your way against Spring's — and every step is friction: you write more code, hit edge cases Spring's happy path avoids, and end up with something no other Spring developer recognises. The instinct to cultivate: when something in Spring feels like a fight — you are writing lots of workaround code, disabling defaults, wrestling the container — stop and ask whether you are going against the grain, because usually there is an idiomatic way that is far simpler.

Concrete ways people fight Spring

The recurring ones, each a real source of pain:

  • Re-implementing what Spring provides. Hand-rolled singletons, factories, a repository over Spring Data, your own event bus, your own transaction handling. Spring gives you all of these (the "Spring is the pattern" lesson) — reimplementing them is pure friction. Use the framework's version.
  • Disabling defaults you do not understand. csrf().disable() copied blindly, turning off auto-configuration to "take control", ddl-auto=create in production. The defaults are usually right and safe; disabling them without understanding why they exist re-opens the problems they solve.
  • new-ing beans instead of injecting them. Constructing a new ParcelService(...) by hand inside another class, bypassing the container — now it has no injected dependencies, no transactions, no proxying. Let Spring create and inject beans; do not instantiate them yourself.
  • Fighting the persistence context. Detaching and re-attaching entities to force behaviour, disabling dirty checking, manual flushing everywhere — usually a sign of not understanding the persistence context (which, understood, does the right thing). Work with JPA's model, not around it.
  • Configuration gymnastics. Elaborate custom configuration to achieve what a property or a starter does out of the box. Check for the idiomatic switch before building machinery.

Each of these is someone imposing their own approach where Spring already had a simpler, conventional one.

Convention over configuration

Spring Boot's core philosophy is convention over configuration: sensible defaults so you configure only what differs from the norm. Fighting this looks like configuring things that already have a good default, or structuring a project against Spring's expectations (components outside the scanned package, non-standard layouts) and then wiring around the resulting breakage. Working with it means: follow the conventions (package layout under the main class, standard property names, starters for capabilities), and configure only the genuine exceptions. The reward is that most of your app "just works", and the parts you configure are the parts that genuinely need it — not machinery to undo defaults you could have kept.

When bending Spring is justified — and how

This is not "never deviate". Sometimes you have a genuine need Spring's happy path does not cover — a specific performance requirement, an unusual integration, a real constraint. Deviating is fine then, but do it knowingly:

  • Understand the convention first — know what you are overriding and why it exists, so you are making an informed trade, not cargo-culting a workaround.
  • Deviate as narrowly as possible — override the one thing you must, keep everything else idiomatic.
  • Document why — a comment explaining the non-standard choice saves the next developer (and future you) from "why is this weird?".

The difference between a justified deviation and fighting the framework is understanding and scope: a narrow, understood, documented override for a real need is engineering; broad workarounds against conventions you have not understood is fighting the grain. When in doubt, do it the Spring way — it is almost always simpler, and the times it genuinely is not are rarer than they feel. The most productive Spring developers are the ones who stopped fighting it.

Check your work

Work with the grain. A framework has a way it expects things done; with it, conventions do the work and everything is easy; against it, every step is friction. Feeling like you are fighting Spring is the signal to look for the idiomatic way.

Common fights. Re-implementing what Spring provides (singletons/factories/repository/events/transactions); disabling defaults you do not understand (csrf().disable(), ddl-auto=create in prod); new-ing beans instead of injecting; fighting the persistence context; configuration gymnastics for what a property/starter does.

Convention over configuration. Follow Spring Boot's conventions (package layout, property names, starters) and configure only genuine exceptions; do not build machinery to undo good defaults.

Justified deviation. Deviate only for a real need, knowingly: understand the convention you override, override as narrowly as possible, document why. Understanding + narrow scope distinguishes engineering from fighting the grain.

Practice

  1. Find a place where code news a bean instead of injecting it; refactor to injection and note what it regains (dependencies, transactions, proxying).
  2. Find a disabled default (csrf().disable(), an excluded auto-configuration) and decide whether it is a justified, understood deviation or a blind workaround.
  3. Identify a piece of custom configuration that a property or starter would replace; simplify it.
  4. Take a component placed outside the scanned package (needing extra wiring) and move it into the convention.
  5. Find a spot fighting the persistence context (manual detach/flush) and reason about the idiomatic alternative.
  6. For a genuine deviation you have made or seen, write the one-line comment justifying it — and check it is as narrow as possible.

Official documentation

Next: the patterns you do not need.

Stuck on this lesson?

Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.

About the internship