0% found this document useful (0 votes)
3 views45 pages

Java To SpringBoot Bridge 2

Uploaded by

chaitanya.e369
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
3 views45 pages

Java To SpringBoot Bridge 2

Uploaded by

chaitanya.e369
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

A FIRST-PRINCIPLES REFERENCE EDITION 1 · 2026

BRIDGE

J AVA SPRING BOOT


THE LANGUAGE THE FRAMEWORK

The
Java → Spring Boot
Bridge
Every Java concept you need before Spring Boot starts to make sense.
Problem → Syntax → Internals → Production.

Object-Oriented Java Type System & Modern Java


I 11 topics · classes, interfaces, II 10 topics · generics, collections,
equals/hashCode streams

Concurrency Basics Bridge to Spring Boot


III 3 topics · threads, locks, executors
IV 6 topics · JDBC, JPA, DI, IoC

Built for engineers transitioning into backend roles


30 45
Java 17 (LTS) · Spring Boot 3.x compatible mental model TOPICS PA G E S
Contents
Each topic answers four questions: Why does it exist? What is the syntax? What happens internally? How is it used in Spring?

PART I · OBJECT-ORIENTED JAVA PART II · TYPE SYSTEM & MODERN JAVA

1. Class & Object (the molecule) 04 12. Generics 18

2. Encapsulation 05 13. Collections Framework 20

3. Inheritance & super 06 14. Comparable vs Comparator 22

4. Polymorphism 08 15. Enums 23

5. Abstraction & Abstract Classes 09 16. Exception Handling 24

6. Interfaces 10 17. Functional Interfaces & Lambdas 26

7. static & final 12 18. Streams API 27

8. Access modifiers 13 19. Optional 29

9. Constructors & this 14 20. Annotations 30

10. equals, hashCode, toString 15 21. Reflection 31

11. Object class & instanceof 16

PART III · CONCURRENCY BASICS PART IV · BRIDGE TO SPRING BOOT

22. Thread & Runnable 33 25. JDBC 37

23. synchronized & volatile 34 26. Connection Pooling 38

24. ExecutorService 35 27. JPA & Hibernate (concept) 39

28. Maven & the JAR 40

29. Dependency Injection (concept) 41

30. Inversion of Control & the Container 42

Cheatsheets 43
PA R T
ONE

Object-Oriented
Java
OOP is not "classes and inheritance." It is a strategy for organizing a
program so that change in one place does not break another. Every
concept below — encapsulation, polymorphism, interfaces — is a tool for
managing that risk.

Java → Spring Boot Bridge 3


01 Class & Object — the smallest unit ~1 min

THE PROBLEM

Pain A program needs to model many things of the same kind — users, orders, requests — each with its own data but
identical behavior. Copy-pasting fields and functions for each one is unmanageable.
Why classes A class is a blueprint: it declares what data each thing has and what it can do. An object is one concrete thing
built from that blueprint, with its own copy of the data. Define the shape once; stamp out as many instances as needed.

S Y N TA X

JAVA
public class User { // blueprint
private String name; // field (data each User holds)
public String getName() { return name; } // method (behavior)
}
User u = new User(); // `new` builds one object from the blueprint

W H AT NEW A C T U A L LY D O E S

Behind the scenes


1. JVM finds the User class file (loads it if not yet loaded). 2. Allocates a chunk of memory on the heap big enough to hold one
User 's fields. 3. Runs the constructor to initialize those fields. 4. Returns the address of that chunk. The variable u sits on the
stack and holds that address — it is a reference, not the object itself.

PRODUCTION REALITY

▶ In Spring Boot
You almost never write new UserService() in production code. The framework creates these objects for you and hands them to
whoever needs them (this is dependency injection, covered in Topic 29). But the class you write is still a blueprint — Spring just owns
the "stamping out" step.

Java → Spring Boot Bridge 4


02 Encapsulation — hide the wiring ~2 min

THE PROBLEM

Pain If any code anywhere can directly read or write any field of any object, then a single rename or rule change (e.g.
"balance can never be negative") forces you to hunt every caller. The blast radius of every change is the whole codebase.
Why encapsulation Mark fields private so outside code cannot touch them. Expose only public methods. Now there is
one funnel through which all reads and writes flow — and that funnel can enforce rules, validate, log, or change
implementation, with zero impact on callers.

S Y N TA X

JAVA
public class Account {
private double balance; // hidden
public void deposit(double amt) {
if (amt <= 0) throw new IllegalArgumentException();
balance += amt; // rule enforced HERE, once
}
public double getBalance() { return balance; }
}

DIAGRAM

private
deposit() getBalance()
balance

The world only sees the rim. The core is unreachable.

C O M M O N M I S TA K E

"Just adding getters and setters for everything"


A setBalance(double) that blindly assigns has thrown away every benefit of private . Encapsulation is about controlling
access, not relabelling it. If a field has no rule, ask whether it should be exposed at all.

PRODUCTION REALITY

▶ In Spring Boot
Entity classes ( @Entity User ) keep fields private and expose accessors. JPA uses these accessors via reflection. Validation
rules ( @NotNull , @Email ) sit on those private fields — the rule lives with the data, exactly the encapsulation principle.

Java → Spring Boot Bridge 5


03 Inheritance & super ~2 min

THE PROBLEM

Pain Two classes share 80% of their fields and methods. Duplicating that 80% means bugs must be fixed in two places.
Forever.
Why inheritance Put the shared 80% in a parent class. A child class extends the parent and automatically gets all its
fields and methods, plus adds its own. One place to fix, one place to change.

S Y N TA X

JAVA
class Animal { void breathe() { … } }
class Dog extends Animal { // Dog gets breathe() for free
void bark() { … } // plus its own methods
}

DIAGRAM

Animal
+ breathe()

"is-a"

Dog Cat Fish


+ bark() + meow() + swim()

A Dog is-an Animal. The arrow points from child to parent.

OVERRIDING & SUPER

A child can override a parent method — write the same signature with new behavior. Inside the child, [Link]() calls the
parent's version. Useful when you want to extend, not replace.
JAVA
class Dog extends Animal {
@Override void breathe() {
[Link](); // run parent's logic first
[Link]("panting"); // then add to it
}
}

C O M M O N M I S TA K E

Inheritance for code reuse, not for "is-a"


Tempting: class Stack extends ArrayList "to get the list methods for free." Now your Stack exposes add(index, item)
— meaningless for a stack. The rule: extend only when the child is truly a kind of the parent and obeys every contract the parent
promises. Otherwise, compose (hold an ArrayList as a private field) instead.

PRODUCTION REALITY

▶ In Spring Boot

Java → Spring Boot Bridge 6


Spring uses inheritance sparingly in user code. Classic patterns: extends RuntimeException for custom exceptions, extends
WebMvcConfigurer for config overrides. The framework prefers composition + interfaces over inheritance — which is the modern
consensus.

Java → Spring Boot Bridge 7


04 Polymorphism — one name, many shapes ~2 min

THE PROBLEM

Pain You have a method that processes a list of "things." If you have to write an if -chain for every possible type — if
(thing instanceof Dog) … else if (Cat) … else if (Fish) — adding a new type forces edits everywhere.

Why polymorphism Write code against a parent type. Call [Link]() on each. The JVM dispatches to the actual
child's version at runtime. Adding a new Cow class doesn't touch the dispatch code — the new behavior plugs in
automatically.

S Y N TA X

JAVA
Animal a = new Dog(); // reference type Animal, actual object Dog
[Link](); // prints "bark" — JVM picks Dog's sound() at runtime

W H AT H A P P E N S I N T E R N A L LY

Behind the scenes — vtable dispatch


Every object holds a hidden pointer to its class's method table (vtable): an array of function pointers indexed by method ID. When
you call [Link]() , the JVM doesn't look at the variable's declared type ( Animal ) — it follows a 's pointer to the actual object,
reads that object's class pointer, and calls the function stored at the sound slot. This is called dynamic dispatch. Result: same call
site, different behavior, decided at runtime.

DIAGRAM

"bark"
new Dog()

"meow" [Link]() new Cat()

new Cow()
"moo"

One call site. Three behaviors. Decided at runtime by the object's actual class.

PRODUCTION REALITY

▶ In Spring Boot
This is the engine of the entire framework. You declare a UserRepository interface; Spring injects whichever implementation it
wants (a JPA one in prod, a mock in tests). Your service code is identical in both cases. That swap is polymorphism, end of story.

Java → Spring Boot Bridge 8


05 Abstraction & Abstract Classes ~2 min

THE PROBLEM

Pain You want a parent class that defines a partial template — some behavior is shared, but a key step varies per child and
must not be left to defaults. If the parent is just a normal class, someone might instantiate it directly and skip the variable step
entirely.
Why abstract Mark the class abstract — the JVM refuses new ParentClass() . Mark the variable methods abstract
— any concrete child must implement them or it too becomes abstract. You publish a half-built skeleton; the children are forced
to finish it.

S Y N TA X

JAVA
abstract class Payment {
public final void process() { validate(); charge(); log(); } // shared
protected abstract void charge(); // children MUST fill this
}

A concrete child writes class UpiPayment extends Payment { protected void charge() { … } } and gains the shared
process() for free.

A B S T R A C T C L A S S V S I N T E R FA C E ( P R E V I E W )

ABSTRACT CLASS INTERFACE

Can hold state (fields), constructors, and concrete methods No instance state. Used to declare a capability contract. Default methods
that share logic. exist but are limited.

A class can extend only one. A class can implement many.

Pick when children share real code as well as a contract. Pick when only the contract matters (and possibly multiple unrelated
capabilities).

PRODUCTION REALITY

▶ In Spring Boot
Pattern in payment / notification systems: abstract class NotificationSender has a concrete send() that calls
format() and deliver() . EmailSender , SmsSender , SlackSender extend it and implement only the abstract pieces.
This is the Template Method pattern, and it is everywhere in Spring's own source ( AbstractApplicationContext ,
JdbcTemplate 's superclass, etc.).

Java → Spring Boot Bridge 9


06 Interfaces — the contract ~3 min

THE PROBLEM

Pain 1 Two unrelated classes (a FileLogger and a CloudLogger ) both need to be usable wherever "something that can
log" is expected. They share no parent and inheriting from one logger doesn't make sense.
Pain 2 Single inheritance: a class can extends only one parent. But often a class needs to be many things at once —
comparable, serializable, runnable.
Why interfaces An interface declares what a class can do (a list of method signatures) without saying how. Any class can
implement any number of interfaces. The caller depends on the interface, not the class — so you can swap implementations
freely.

S Y N TA X

JAVA
public interface Logger {
void log(String msg); // no body — just the contract
}
class FileLogger implements Logger {
public void log(String msg) { /* write to disk */ }
}

L I N E - B Y- L I N E

public interface Logger Declares a type named Logger. It is not a class — you cannot say new Logger() .

void log(String msg); An abstract method — signature only, ends with a semicolon, no body. Every
implementer must provide one.

class FileLogger implements Logger Promises that FileLogger fulfills the Logger contract. Compiler will refuse to build until
log() is provided.

public void log(String msg) {…} The actual implementation. Now anywhere code asks for a Logger , a
FileLogger is accepted.

D E FA U LT M E T H O D S ( J AVA 8 + )

Originally interfaces had only abstract methods. Java 8 added default methods (with a body) so an interface can ship reusable
helpers without forcing every implementer to write them — and without breaking older implementations when the interface evolves.
JAVA
interface Logger {
void log(String msg);
default void logError(String m) { log("ERROR: " + m); } // optional override
}

M U LT I P L E I M P L E M E N TAT I O N

JAVA
class Task implements Runnable, Comparable<Task>, Serializable { … }
// Task is now usable as any of these three things, in any context.

C O M M O N M I S TA K E

"Should this be an interface or an abstract class?"


Default to interface. Reach for abstract class only when children genuinely share code, not just a contract. Most "design pain"
comes from picking abstract class too early — it locks the children into a single inheritance slot for life.

PRODUCTION REALITY

▶ In Spring Boot — interfaces are the entire programming model

Java → Spring Boot Bridge 10


Repository layer: you write interface UserRepository extends JpaRepository<User, Long> { } — no implementation.
Spring Data generates one at startup using reflection + proxies.
Service layer: interface PaymentService with a single UpiPaymentService implements PaymentService in prod and a
FakePaymentService implements PaymentService in tests. Same controller code, different injected bean.

Why everywhere? Spring's superpower is swapping implementations without touching callers — and the only swap point Java offers
cleanly is "a variable typed by an interface."

Java → Spring Boot Bridge 11


07 static & final ~2 min

T H E P R O B L E M T H E Y S O LV E

Pain — static Some data or behavior belongs to the class itself, not to any single instance. Example: a counter of how many
User s have ever been created, or a utility function like [Link] that has no per-instance state.

Why static Tied to the class, not to objects. One copy total. Accessed as [Link] , no new needed.

Pain — final A reference or value must not change after initialization. Without enforcement, somewhere down the line,
someone reassigns it.
Why final The compiler refuses any reassignment. On a class: cannot be extended. On a method: cannot be overridden.

S Y N TA X

JAVA
public class Counter {
public static int total = 0; // shared across all instances
private final int id; // per-instance, set once, never reassigned
public Counter() { id = ++total; } // final must be set in constructor
}

THREE THINGS TO KNOW

CONCEPT WHAT IT MEANS

static field One copy per class. Lives in the class's metadata in memory, not in any object.

static method Has no this . Cannot access non-static fields directly. Pure utility.

final The variable can't be reassigned. The object it points to can still mutate. final List<X> xs — you can still call
reference [Link](…) .

PRODUCTION REALITY

▶ In Spring Boot
Constructor-injected dependencies are almost always private final — once Spring wires them at startup, they should never be
reassigned. Constants live as public static final . Utility classes ( StringUtils , CollectionUtils ) are full of static
methods.

Java → Spring Boot Bridge 12


08 Access modifiers ~1 min

THE PROBLEM

Pain Without visibility control, every class can call every method of every other class. Internal helpers leak into the public
API. Refactoring becomes terrifying because anyone might depend on anything.
Why modifiers Java lets you mark each field, method, and class with how widely it is visible. Pick the narrowest that still
works. The narrower the visibility, the smaller the blast radius of a change.

THE FOUR LEVELS

MODIFIER SAME CLASS SAME PACKAGE SUBCLASS (ANY PACKAGE) ANYWHERE

private ✓ — — —

(no modifier — "package-private") ✓ ✓ — —

protected ✓ ✓ ✓ —

public ✓ ✓ ✓ ✓

THE RULE OF THUMB

Start private . Loosen only when forced. A field never needs to be public — that is what getters are for. A class's "API to the
world" should be a small set of public methods; everything else is implementation noise.

PRODUCTION REALITY

▶ In Spring Boot
@Service and @RestController classes are public (the framework must see them). Their methods follow the same rule:
only the methods that are HTTP endpoints or that other components call need to be public. Helper methods stay private .

Java → Spring Boot Bridge 13


09 Constructors & this ~2 min

THE PROBLEM

Pain An object created in a half-built state (some fields set, some not) is a bug waiting to happen. Worse: callers must
"remember" which setters to call in which order.
Why constructors A constructor is a special method that runs once when an object is created and is the single chance to
put it into a valid initial state. Anything the object needs to function should be a constructor parameter.

S Y N TA X

JAVA
public class User {
private final String email;
public User(String email) { // constructor — name matches the class, no return type
[Link] = email; // `this` disambiguates field from parameter
}
}

RULES YOU MUST KNOW

If you write no constructor, Java gives you a free no-argument one. Write any constructor at all, and that freebie disappears.
You can have many constructors (overloading) with different parameter lists.
this(...) calls another constructor of the same class — must be the first line.
super(...) calls the parent constructor — also must be the first line. If you skip it, Java inserts super() implicitly.

C O M M O N M I S TA K E

Doing real work in the constructor


Hitting a database, calling an HTTP endpoint, or spawning a thread inside a constructor turns "creating an object" into a slow, failure-
prone operation — and it runs before the object is fully built. Keep constructors to assignment and trivial validation. Real work goes in
dedicated methods called afterwards.

PRODUCTION REALITY

▶ In Spring Boot — constructor injection is the standard


You declare dependencies as constructor parameters. Spring sees the constructor, finds matching beans, and passes them in. Fields
are private final — set once, never null, immune to test-bypass.

JAVA
@Service
public class UserService {
private final UserRepository repo;
public UserService(UserRepository repo) { [Link] = repo; }
}

Java → Spring Boot Bridge 14


10 equals, hashCode, toString ~3 min

THE PROBLEM

Pain — equals Two User objects with the same email should be considered equal. By default Java's == compares
memory addresses, so two freshly-created users are never equal even if every field matches.
Pain — hashCode HashMap and HashSet rely on a number derived from the object's content to know which bucket to
store it in. If equal objects produce different hash codes, the map breaks — it will store duplicates and fail to find what is
already there.

Why override You teach Java what equality means for your type, and you keep equality and hashing consistent.

THE CONTRACT (MEMORIZE THIS)

RULE WHY

If [Link](b) is true, [Link]() == [Link]() must also be Otherwise hash-based collections cannot find equal
true. items.

Equal hash codes do not imply equal objects. Hashing is many-to-one; collisions are normal.

If you override equals , you must override hashCode . Forgetting this is one of the top three Java bugs.

S Y N TA X

JAVA
@Override
public boolean equals(Object o) {
if (!(o instanceof User u)) return false;
return [Link](email, [Link]); // content equality
}
@Override public int hashCode() { return [Link](email); }

THE SHORTCUT — RECORD ( J AVA 1 4 + )

JAVA
public record User(String email, String name) {}
// generates: final fields, constructor, accessors, equals, hashCode, toString.

A record is Java's answer to "immutable data carrier." Use freely for DTOs, value objects, query results.

PRODUCTION REALITY

▶ In Spring Boot
JPA entities must implement equals and hashCode correctly because Hibernate stores them in Set s for relationship
management. The safe pattern: equality based on a stable business key (email, order number), not the database id (which is null
before insert).

For DTOs returned by REST endpoints, prefer record s — concise, immutable, equality and serialization come free.

Java → Spring Boot Bridge 15


11 The Object root & instanceof ~1 min

THE IDEA

Every class in Java — yours, the standard library's, the framework's — silently extends Object . That is why every object
has equals , hashCode , toString , getClass : they are defined once on Object and inherited universally.

Consequence: a method that takes Object accepts anything. This is the original (pre-generics) way Java did "collections of
anything."

INSTANCEOF & PAT T E R N VA R I A B L E S

JAVA
if (o instanceof User u) { // since Java 16 — also binds 'u'
[Link]([Link]()); // no cast needed
}

Older code does if (o instanceof User) { User u = (User) o; … } . The modern form fuses the check and the cast.

C O M M O N M I S TA K E

Writing chains of instanceof


If you find yourself writing if (x instanceof A) … else if (x instanceof B) … , you have rediscovered polymorphism —
badly. Move the per-type behavior into a method on each type and call it through a common interface.

Java → Spring Boot Bridge 16


PA R T
TWO

Type System &


Modern Java
OOP gives you structure. The type system gives you safety — bugs
caught at compile time, not 2 a.m. on Friday. The "modern" features
(lambdas, streams, Optional) are what make Java code look like Java
code in 2026.

Java → Spring Boot Bridge 17


12 Generics — typed containers ~4 min

THE PROBLEM

Pain Before generics, a List stored Object . To use an item you cast it back: String s = (String) [Link](0); .
If you mistakenly put an Integer in, the cast blows up at runtime with ClassCastException . The compiler couldn't help
because it had no idea what the list was "supposed to" contain.
Why generics Parameterize the type. List<String> means list of String. The compiler now refuses to put anything but
Strings in, and removes the cast on the way out. Bugs that used to happen at runtime now fail at compile time.

S Y N TA X — U S I N G G E N E R I C T Y P E S

JAVA
List<String> names = new ArrayList<>();
[Link]("Aditya");
String first = [Link](0); // no cast, no runtime check

S Y N TA X — W R I T I N G Y O U R O W N

JAVA
public class Box<T> { // T is a type parameter — a placeholder
private T value;
public void set(T v) { value = v; }
public T get() { return value; }
}
// Usage: Box<Integer> b = new Box<>(); [Link](42); int x = [Link]();

W H AT H A P P E N S I N T E R N A L LY — T Y P E E R A S U R E

Behind the scenes


Generics live only at compile time. The compiler checks types, then erases them: List<String> becomes plain List in the
bytecode, and the get-site has a synthetic cast inserted. The JVM never sees the <String> .
Why it matters: at runtime you cannot ask "is this a List<String> ?" — the answer is gone. You also cannot do new T() inside
a generic class. These are surprising the first time you hit them.

WILDCARDS — ? EXTENDS AND ? SUPER

A subtle but unavoidable detail: List<Dog> is not a subtype of List<Animal> , even though Dog is an Animal. Reason: if it were,
you could add a Cat through the Animal-typed reference, corrupting the Dog list.
Wildcards loosen this when needed:

FORM READ AS USE WHEN…

List<? extends Animal> "some list of Animal or subtype — but I don't know which" You only read from it.

List<? super Dog> "some list of Dog or supertype" You only write into it.

Mnemonic: PECS — Producer extends , Consumer super .

PRODUCTION REALITY

▶ In Spring Boot — generics are how every API expresses itself

JAVA
interface UserRepository extends JpaRepository<User, Long> { }
// <User, Long> tells Spring: entity type is User, primary key is Long.

ResponseEntity<User> getUser() { … }
// "response wrapping a User payload"

Java → Spring Boot Bridge 18


You can't read a single Spring page without seeing <…> . Generics make it possible for the framework to know the types of your
entities, DTOs, and responses without you telling it twice.

Java → Spring Boot Bridge 19


13 Collections Framework ~3 min

THE PROBLEM

Pain Storing groups of objects is universal — but the right data structure depends on the operation pattern. A naive
[Link]() scan over a million items is slow; the right answer was a HashSet all along.

Why a framework Java provides a unified hierarchy: interfaces describe what a collection can do; classes provide how. You
program against the interface ( List ) and pick the implementation ( ArrayList ) based on access pattern, with O-notation in
mind.

T H E H I E R A R C H Y AT A G L A N C E

Collection

List Set Queue

ArrayList HashSet ArrayDeque


LinkedList TreeSet PriorityQueue
LinkedHashSet

Map (separate hierarchy: HashMap · TreeMap · LinkedHashMap)

Map is not a Collection — it stores pairs, not items.

W H E N T O P I C K W H I C H ( T H E C H E AT S H E E T )

NEED PICK WHY

Indexed, ordered, frequent reads ArrayList Backed by an array. O(1) get, O(n) insert in middle.

Frequent insert/remove at ends ArrayDeque Outperforms LinkedList in practice.

Uniqueness, fast lookup, no order needed HashSet Hash-bucketed. O(1) contains .

Uniqueness + insertion order LinkedHashSet HashSet plus a linked list of entries.

Uniqueness + sorted TreeSet Red-black tree. O(log n).

Key → value mapping HashMap The workhorse. O(1) average put/get.

Map but preserves insertion order LinkedHashMap Useful for LRU caches.

Map sorted by key TreeMap O(log n), range queries possible.

S Y N TA X — T H E PAT T E R N S Y O U ' L L W R I T E D A I LY

JAVA
List<String> names = new ArrayList<>([Link]("a", "b"));
Set<Integer> ids = new HashSet<>();
Map<String, User> byEmail = new HashMap<>();
[Link]("a@[Link]", user);
User u = [Link]("x@[Link]", null);

C O M M O N M I S TA K E

Java → Spring Boot Bridge 20


Using a HashSet / HashMap with a mutable key
The bucket is chosen from hashCode() at insert time. Mutate the key afterwards and the hash changes — the item is still in the
bucket it was stored in, but lookups go to a different bucket. The item becomes invisible. Keys must be effectively immutable.

PRODUCTION REALITY

▶ In Spring Boot
Repository queries return List<User> . Configuration properties bind to Map<String, String> . Caches are often
ConcurrentHashMap s. [Link](…) and [Link](…) create immutable collections — use them for fixed config to prevent
accidental mutation.

Java → Spring Boot Bridge 21


14 Comparable vs Comparator ~2 min

THE PROBLEM

Pain To sort a list of User s, Java needs to know which one comes first. There is no universal answer — sometimes by
name, sometimes by signup date, sometimes by revenue. You must teach it.
Two strategies Comparable — the class has one natural order, baked in (e.g. String alphabetical, Integer numeric).
Comparator — order supplied separately, externally, possibly many of them.

S Y N TA X

JAVA
// Comparable — natural order lives on the class
class User implements Comparable<User> {
public int compareTo(User o) { return [Link]([Link]); }
}

// Comparator — order built on demand, doesn't touch the class


[Link]([Link](User::getSignupDate).reversed());

THE COMPARETO CONTRACT

Return negative if this is less, zero if equal in order, positive if greater. Must be consistent with equals (or sorted collections
behave strangely).

PRODUCTION REALITY

▶ In Spring Boot
Repository queries usually sort at the database with [Link]("createdAt").descending() — pushed into SQL, not done in
Java memory. In-memory sorting (lists already loaded) uses Comparator . PriorityQueue (e.g. for retry scheduling) needs a
Comparator at construction.

Java → Spring Boot Bridge 22


15 Enums — a closed set of values ~1 min

THE PROBLEM

Pain Status fields stored as String ("PENDING", "PAID", "FAILED") invite typos that compile fine and crash in prod.
There is no compiler help to catch "PADING" .
Why enums An enum declares a fixed list of named instances. The type is its own — only those values are valid. The
compiler catches anything else.

S Y N TA X

JAVA
public enum OrderStatus { PENDING, PAID, SHIPPED, CANCELLED }
OrderStatus s = [Link]; // the only legal values

E N U M S C A N C A R RY D ATA A N D M E T H O D S

JAVA
public enum Plan {
FREE(0), PRO(9), TEAM(29);
private final int price;
Plan(int p) { [Link] = p; }
public int price() { return price; }
}
// Usage: int p = [Link](); // 9

PRODUCTION REALITY

▶ In Spring Boot
Map enum to DB column with @Enumerated([Link]) — stores the name ("PAID"), survives reordering. Avoid the
default ORDINAL : it stores the index, so reordering enum constants silently corrupts old data.

Java → Spring Boot Bridge 23


16 Exception Handling ~3 min

THE PROBLEM

Pain A method ten layers deep hits an unexpected condition — file missing, network down, divide by zero. Returning a
magic null or an error code forces every caller to check for it, and one forgotten check corrupts the whole flow.
Why exceptions Java provides a separate channel for failure. A method throws an exception; control jumps to the nearest
matching catch up the call stack. Normal logic stays clean; error logic is centralized.

THE HIERARCHY

Throwable

Error (don't catch) Exception (checked)

OutOfMemoryError
StackOverflowError
RuntimeException
NullPointerException · IllegalArgument · IllegalState

Checked = compiler forces you to catch or throws. Unchecked (RuntimeException) = no compile-time requirement.

S Y N TA X

JAVA
try {
var data = [Link](path); // throws IOException (checked)
} catch (IOException e) {
[Link]("read failed", e);
throw new UncheckedIOException(e); // rethrow as unchecked
} finally {
// runs whether try succeeded or threw — cleanup goes here
}

CHECKED VS UNCHECKED — WHEN TO USE WHICH

CHECKED (EXTENDS EXCEPTION) UNCHECKED (EXTENDS RUNTIMEEXCEPTION)

Caller can reasonably recover — e.g. retry, fall back. Programming error or unrecoverable condition. Caller shouldn't have to
Compiler enforces handling. wrap every call site.

Examples: IOException , SQLException . Examples: NullPointerException , IllegalArgumentException .

Modern style and Spring strongly prefer unchecked exceptions — they don't pollute method signatures and don't tempt people to write empty
catch blocks just to make code compile.

T RY- W I T H - R E S O U R C E S

JAVA
try (var conn = [Link](url)) {
// use conn — auto-closed when block exits, even on exception
}

Works with any type implementing AutoCloseable . This is how leaks of connections, streams, and file handles are prevented.

C O M M O N M I S TA K E

Java → Spring Boot Bridge 24


Catch & swallow
catch (Exception e) { } — an empty block. Errors vanish; the program "succeeds" while producing wrong output. If you
cannot meaningfully handle an exception, do not catch it. Let it propagate to a place that can.

PRODUCTION REALITY

▶ In Spring Boot — global handlers replace try/catch

JAVA
@RestControllerAdvice
public class GlobalErrors {
@ExceptionHandler([Link])
ResponseEntity<String> notFound(UserNotFoundException e) {
return [Link](404).body([Link]());
}
}

Throw a domain exception from anywhere; this handler converts it to an HTTP response. Controllers stay free of error plumbing.

Java → Spring Boot Bridge 25


17 Functional Interfaces & Lambdas ~3 min

THE PROBLEM

Pain Passing behavior as a value used to require a full anonymous inner class — 6 lines of ceremony for a one-line idea
like "compare two strings by length." Code that should read as a transformation read as a wall of boilerplate.
Why lambdas (Java 8+) A short syntax for "a function as a value." But Java's type system has no first-class function type — so
each lambda is silently treated as an instance of a single-method interface called a functional interface.

S Y N TA X

JAVA
// Before:
Comparator<String> c = new Comparator<>() {
public int compare(String a, String b) { return [Link]() - [Link](); }
};
// After:
Comparator<String> c = (a, b) -> [Link]() - [Link]();

B U I LT- I N F U N C T I O N A L I N T E R FA C E S ( M E M O R I Z E )

INTERFACE SHAPE USED FOR

Function<T,R> T → R Transform a value into another

Predicate<T> T → boolean Test a condition

Consumer<T> T → void Do something with a value

Supplier<T> () → T Produce a value on demand

BiFunction<T,U,R> (T,U) → R Two-arg version of Function

METHOD REFERENCES — EVEN SHORTER

JAVA
[Link]([Link]::println); // shorthand for x -> [Link](x)
[Link]().map(User::getEmail); // shorthand for u -> [Link]()

PRODUCTION REALITY

▶ In Spring Boot
Lambdas show up wherever you "configure with a function": .filter(u -> [Link]()) in streams,
[Link](...) response extractors, security DSL [Link](req ->
[Link]().authenticated()) . The "DSL feel" of modern Spring config is built on functional interfaces.

Java → Spring Boot Bridge 26


18 Streams API ~3 min

THE PROBLEM

Pain "From this list of orders, give me the emails of users who placed an order over ₹10,000 last month, sorted
alphabetically, distinct." Written with nested loops and intermediate lists, this is 20 lines of bookkeeping where the intent
drowns in the mechanics.
Why streams (Java 8+) Describe the pipeline as a chain of operations: filter, map, sort, distinct, collect. The implementation
handles iteration. Code reads top-to-bottom like the English sentence above.

S Y N TA X

JAVA
List<String> emails = [Link]()
.filter(o -> [Link]() > 10000)
.map(o -> [Link]().getEmail())
.distinct()
.sorted()
.toList();

T H E P I P E L I N E M E N TA L M O D E L

source filter() map() sorted() toList()

intermediate (lazy) intermediate (lazy) intermediate (lazy) terminal (runs)

W H AT " L A Z Y " M E A N S

Behind the scenes


Intermediate operations ( filter , map , sorted ) do nothing on their own — they only build up a description of work. The
terminal operation ( toList , count , forEach ) is what triggers actual iteration. The stream then pulls each element through the
entire pipeline one at a time. Stream with no terminal → nothing runs.

C O M M O N T E R M I N A L O P E R AT I O N S

.toList() Collect into a list

.collect([Link](k,v)) Collect into a map

.count() Number of elements

.findFirst() / .findAny() Returns an Optional

.reduce(0, Integer::sum) Fold to a single value

.anyMatch(p) / .allMatch(p) Existential / universal check

C O M M O N M I S TA K E

Reusing a stream
A stream is consumed exactly once. [Link]() then [Link]() throws IllegalStateException . If you
need both, call .stream() on the source twice.

Java → Spring Boot Bridge 27


PRODUCTION REALITY

▶ In Spring Boot
Service-layer transformations (entity list → DTO list, group orders by user, compute aggregates over already-fetched data) are
textbook streams. Anti-pattern: doing the filtering/aggregation in Java when the database could have done it. Always ask: should this
be a SQL query or repository method instead?

Java → Spring Boot Bridge 28


19 Optional — explicit absence ~2 min

THE PROBLEM

Pain A method that may return null is indistinguishable from one that never does, by signature alone. Callers forget to
null-check; NullPointerException in production at 3am. Tony Hoare, who invented null, calls it his "billion-dollar mistake."
Why Optional (Java 8+) A return type of Optional<User> says at the type level: "this might be empty — you must
decide what to do." The compiler can't be ignored; the API forces a conscious choice.

S Y N TA X

JAVA
Optional<User> maybe = [Link](email);
String name = [Link](User::getName).orElse("anonymous");
[Link](u -> [Link]("found " + u));

THE FEW METHODS WORTH KNOWING

isPresent() / isEmpty() Boolean check (use sparingly — defeats the point)

get() Returns the value or throws if empty. Avoid outside tests.

orElse(default) Value or a fallback (eagerly evaluated)

orElseGet(supplier) Value or compute fallback lazily

orElseThrow(() -> new …) Value or raise a domain exception

map(fn) / flatMap(fn) Transform the contained value if present

ifPresent(consumer) Run a side effect if non-empty

C O M M O N M I S TA K E

Using Optional as a field or method parameter


Designed only as a return type. As a field it adds overhead and serialization headaches. As a parameter it shifts the null-check
burden onto the caller. Use null internally; expose Optional at the API boundary if a value might be missing.

PRODUCTION REALITY

▶ In Spring Boot
The standard Spring Data signature: Optional<User> findById(Long id) . The conventional handling:
[Link](id).orElseThrow(() -> new UserNotFoundException(id)) — clean, explicit, and the global handler turns
the exception into a 404.

Java → Spring Boot Bridge 29


20 Annotations ~3 min

THE PROBLEM

Pain Frameworks need a way to tell things about your code: "this method is a unit test", "this class is a REST controller",
"this field maps to the email column." The alternatives are XML files (verbose, far from the code) or magic naming
conventions (fragile).
Why annotations (Java 5+) A small piece of metadata attached directly to a class/method/field. The compiler stores it; tools
and frameworks read it at build time or runtime via reflection. Annotations themselves do nothing — they are tags that other
code looks for.

U S I N G A N A N N O TAT I O N

JAVA
@Deprecated
public void oldMethod() { … } // compiler warns callers

@Override
public String toString() { … } // compiler checks parent has this method

WRITING ONE

JAVA
@Retention([Link]) // keep at runtime (so reflection sees it)
@Target([Link]) // only on methods
public @interface Audited { String value() default ""; }

THE THREE RETENTION POLICIES

SOURCE Discarded after compilation. E.g. @Override .

CLASS (default) In the .class file but not loaded into the JVM at runtime.

RUNTIME Available to reflection. This is what frameworks need.

PRODUCTION REALITY

▶ In Spring Boot — annotations are the configuration language

JAVA
@RestController
@RequestMapping("/users")
public class UserController {
@GetMapping("/{id}")
User get(@PathVariable Long id) { … }
}

Each annotation is read at startup by some part of the framework: @RestController by the component scanner, @GetMapping
by the MVC dispatcher, @PathVariable by the argument resolver. The annotations themselves do nothing — Spring's scanning
code does the work after seeing them.

Java → Spring Boot Bridge 30


21 Reflection — inspecting code at runtime ~2 min

THE PROBLEM

Pain A framework loaded at runtime knows nothing about your classes. It cannot say new UserService() — it has never
heard of UserService . Yet it must instantiate it, look at its annotations, find its constructor, and call its methods.
Why reflection The JVM exposes its own metadata. Given a Class<?> object, you can list every field, method, constructor,
annotation — and invoke them by name. Code becomes data.

S Y N TA X ( Y O U U S U A L LY D O N ' T W R I T E T H I S Y O U R S E L F )

JAVA
Class<?> cls = [Link]("[Link]");
Object obj = [Link]().newInstance();
Method m = [Link]("hello");
[Link](obj);

W H Y I T M AT T E R S E V E N T H O U G H Y O U W O N ' T U S E I T D I R E C T LY

Every framework you'll touch — Spring, Hibernate, Jackson, JUnit — relies on reflection to discover what your code is.
Understanding this removes the magic. Spring isn't reading minds; it's calling getDeclaredAnnotations() and
getDeclaredConstructors() on the classes it finds during component scanning.

TRADE-OFF

Cost
Reflective calls are slower than direct calls and bypass compile-time checks. Modern frameworks cache reflection results
aggressively at startup — fast steady-state, but startup time is dominated by it. (This is why "Spring Boot startup is 4 seconds.")

PRODUCTION REALITY

▶ In Spring Boot
Component scanning, dependency injection, AOP proxies, request mapping, JPA entity introspection, Jackson serialization — all
reflection underneath. You write declarative annotations; reflection translates them into runtime behavior.

Java → Spring Boot Bridge 31


PA R T
THREE

Concurrency
Basics
A web server handles many requests at once. You don't have to write
threads yourself — Spring's container does — but you must understand
what goes wrong when shared data meets two threads at the same
instant.

Java → Spring Boot Bridge 32


22 Threads & Runnable ~2 min

THE PROBLEM

Pain Single-threaded code can do one thing at a time. While it waits on a slow operation (network, disk), the CPU sits idle
and no other work happens. A web server serving one user at a time would be useless.
Why threads A thread is an independent path of execution within the same process. The OS schedules many of them; they
share heap memory but each has its own stack. Two threads can run two pieces of code at the same time on different CPU
cores.

S Y N TA X

JAVA
Runnable task = () -> [Link]([Link]().getName());
new Thread(task).start(); // runs task on a new thread

Runnable is a functional interface — a lambda is enough.

THE HIDDEN DANGER

What goes wrong


Two threads incrementing the same counter count++ can lose updates. Why: count++ is actually three operations — read, add,
write. Thread A reads 5, thread B reads 5, both write 6. One increment is lost. This is a race condition. The fix is in the next topic.

PRODUCTION REALITY

▶ In Spring Boot
You rarely create threads directly. The web container (Tomcat by default) keeps a pool of threads — typically 200 — and assigns one
to each incoming HTTP request. Your controller and service methods are running on a thread you didn't make. What you must
understand: anything you store in a shared bean's field can be touched by all of them at once.

Java → Spring Boot Bridge 33


23 synchronized & volatile ~2 min

THE PROBLEM & THE TWO TOOLS

Pain 1 — atomicity Multi-step operations (read-modify-write) must run uninterrupted, or threads see half-finished state.

synchronized Wraps a block in a lock. Only one thread holds the lock at a time; others wait. The block becomes
effectively single-threaded.
Pain 2 — visibility A thread may keep a variable in its CPU register and never re-read main memory. Another thread's update
is invisible.
volatile Marks a field as "always read fresh from main memory, always publish writes immediately." Guarantees
visibility, not atomicity.

S Y N TA X

JAVA
private volatile boolean running = true; // visibility

public synchronized void increment() { count++; } // atomicity

WHICH TO PICK

NEED TOOL

Read or write a single flag/reference, no compound logic volatile

Multi-step update, mutual exclusion synchronized

High-performance counters AtomicInteger (lock-free via CAS)

C O M M O N M I S TA K E

Synchronizing too coarsely


Putting synchronized on every public method of a service "for safety" serializes all requests through that bean. Throughput drops
to one-at-a-time. The cure: keep services stateless (no mutable fields). State lives in the database, the cache, or the request — not in
the bean.

PRODUCTION REALITY

▶ In Spring Boot
Singletons (the default bean scope) are shared across all request threads. Stateless beans ( @Service s with only injected
dependencies and no mutable fields) are inherently thread-safe — and this is the recommended style. Reach for
synchronized / ConcurrentHashMap only when you genuinely keep state in a singleton.

Java → Spring Boot Bridge 34


24 ExecutorService & thread pools ~2 min

THE PROBLEM

Pain Creating a new thread per task is expensive (each thread takes ~1MB of memory plus OS overhead). Letting
unbounded threads spawn quickly exhausts memory and crashes the JVM.
Why pools Pre-create a fixed pool of threads. Submit tasks; they queue up and are picked off by available threads. Bounded
resources, bounded blast radius.

S Y N TA X

JAVA
ExecutorService pool = [Link](4);
Future<Integer> f = [Link](() -> expensive());
int result = [Link](); // blocks until done
[Link]();

FUTURE VS COMPLETABLEFUTURE

Future is the basic handle — you can get() (blocking) or cancel() . CompletableFuture (Java 8+) adds composition:
chain .thenApply(...) , .thenCompose(...) , combine multiple futures, handle errors — all without blocking a thread.

PRODUCTION REALITY

▶ In Spring Boot
@Async on a method makes Spring run it on a managed executor. Configure the pool via TaskExecutor . Used for fire-and-forget
background work — sending welcome emails, generating reports — without blocking the request thread that triggered them.

Java → Spring Boot Bridge 35


PA R T
FOUR

Bridge to
Spring Boot
Spring Boot did not invent talking to databases or wiring objects together.
It automates patterns that already existed. To not be afraid of Spring's
magic, you have to first see what it is replacing — raw JDBC, manual
new-ing, scattered configuration. After that, Spring stops being magic.

Java → Spring Boot Bridge 36


25 JDBC — Java's database protocol ~3 min

THE PROBLEM

Pain Every database speaks a different wire protocol. If your code had to know about MySQL's protocol, you couldn't switch
to Postgres without rewriting everything.
Why JDBC A standard Java API for relational databases (Java Database Connectivity). Your code uses Connection ,
Statement , ResultSet — same names regardless of database. Each database ships a driver (a JAR) that implements
the JDBC interfaces against its own protocol. Swap drivers, same code.

T H E F I V E - S T E P F L O W ( E V E RY J D B C I N T E R A C T I O N )

DriverManager Connection PreparedStatement ResultSet close()

S Y N TA X — R AW J D B C , E X A C T LY A S Y O U ' D W R I T E I T

JAVA
try (Connection c = [Link](url, user, pwd);
PreparedStatement ps = [Link]("SELECT name FROM users WHERE id=?")) {
[Link](1, 42);
try (ResultSet rs = [Link]()) {
if ([Link]()) [Link]([Link]("name"));
}
} // try-with-resources closes ps, rs, and c automatically

WHY PREPAREDSTATEMENT , NOT STATEMENT

Concatenating user input into a SQL string is SQL injection — the user types ' OR 1=1 -- and exfiltrates your database.
PreparedStatement uses ? placeholders; values are bound separately and never parsed as SQL. Always use it. No
exceptions.

PRODUCTION REALITY

▶ In Spring Boot
You rarely write raw JDBC. JdbcTemplate wraps the boilerplate; Spring Data JPA goes further and generates SQL from method
names. But everything sits on JDBC underneath. When something breaks deep — connection leaks, driver bugs — you read JDBC
stack traces. Knowing this layer is non-negotiable.

Java → Spring Boot Bridge 37


26 Connection Pooling ~1 min

THE PROBLEM

Pain Opening a database connection takes 50–200 ms — TCP handshake, TLS, authentication. If every HTTP request
opens and closes its own connection, the database is your bottleneck before the second user shows up.
Why pooling Keep N connections open and reusable. Each request "borrows" one, uses it, and returns it. Sub-millisecond
acquisition, capped database load.

M E N TA L M O D E L

Connection Pool
App threads Database
request a conn capped load

green = in use · gray = idle, ready

PRODUCTION REALITY

▶ In Spring Boot
HikariCP is the default pool — wired automatically when you add spring-boot-starter-data-jpa or spring-boot-
starter-jdbc . You configure it via [Link]-pool-size=20 . The single most common
production bug here: code that holds connections too long (e.g. long transactions over slow services), exhausting the pool and
freezing the application.

Java → Spring Boot Bridge 38


27 JPA & Hibernate — the ORM concept ~3 min

THE PROBLEM

Pain Raw JDBC is verbose. For every entity you write boilerplate: SELECT and parse to object, INSERT/UPDATE mapping
fields to columns, manually wire associations (one user has many orders). 80% of database code is mechanical translation.
Why ORM Object-Relational Mapping: declare classes that mirror tables. The ORM library generates SQL for CRUD and
converts rows to objects. You think in objects; the ORM handles the SQL.
JPA vs Hibernate JPA is the standard specification (the interfaces and annotations). Hibernate is one implementation of it —
by far the most common. Spring Boot's default JPA provider is Hibernate.

AN ENTITY (THE BARE MINIMUM)

JAVA
@Entity @Table(name = "users")
public class User {
@Id @GeneratedValue(strategy = [Link])
private Long id;
private String email; // column "email" inferred from field name
// + getters, setters, equals/hashCode (or use @Entity-compatible Lombok)
}

C O R E C O N C E P T S T O U N D E R S TA N D

TERM WHAT IT MEANS

EntityManager The runtime object that talks to the database on the entity's behalf. Manages a "session."

Persistence context The EntityManager 's in-memory cache of currently-loaded entities. Entities here are tracked for changes.

Managed vs Managed: tracked by an open persistence context — mutations auto-flush to DB. Detached: outside any context —
detached mutations are pure Java, no DB effect.

Lazy vs eager By default, relationships load on first access (lazy). Eager ( [Link] ) loads immediately. Lazy is correct
loading most of the time.

T H E FA M O U S F O O T G U N — N + 1 Q U E R I E S

What happens
You load 100 users (1 query), then iterate and access [Link]() on each — that triggers 100 more queries, one per
user. 101 queries to display one page. The fix: JOIN FETCH in a JPQL query, or an entity graph. Watching SQL logs in dev is the
only reliable way to catch this.

PRODUCTION REALITY

▶ In Spring Boot — Spring Data JPA


One layer above raw JPA. You declare interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email); } — and Spring generates the implementation at startup. findByEmail is
parsed from the method name into a query. This is the productivity peak of the entire stack — and the place that most hides what's
really happening, so the JDBC and JPA layers underneath are exactly what you must understand to debug it.

Java → Spring Boot Bridge 39


28 Maven & the JAR — how Java code ships ~2 min

THE PROBLEM

Pain A real Java app uses 50+ libraries (web server, JSON, database driver, logging, validation…). Each has versions;
many depend on each other. Manually downloading and putting JARs on the classpath is fragile and unreproducible — works
on your machine, breaks on the build server.
Why Maven A build tool with a declarative dependency model. You list what you need (group + artifact + version) in
[Link] ; Maven downloads them, plus their dependencies, plus theirs, from a central repository. Same [Link] →
identical build everywhere.

A MINIMAL [Link] SNIPPET

XML
<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.3.0</version>
</dependency>

J A R V S FAT J A R V S WA R

JAR Java Archive. A zip of compiled .class files + resources. The unit of a library.

Fat (uber) A single JAR containing your code and all dependencies' classes. Spring Boot's java -jar [Link] uses this — one
JAR file, runs anywhere with a JVM.

WAR Web Archive. Older deployment model where you drop the file into a separate Tomcat/JBoss. Rarely needed for Spring
Boot.

M AV E N C O M M A N D S Y O U ' L L A C T U A L LY T Y P E

BASH
mvn clean package # compile + tests + produce JAR in target/
mvn spring-boot:run # run the app without packaging
mvn dependency:tree # see the full dependency graph (debug version conflicts)

PRODUCTION REALITY

▶ In Spring Boot
"Starters" ( spring-boot-starter-web , -data-jpa , -security ) are curated dependency bundles. Adding one starter pulls
in 10–30 transitively-correct versions. This is why spring init projects "just work" — somebody else solved the dependency
puzzle.

Java → Spring Boot Bridge 40


29 Dependency Injection — the concept ~3 min

THE PROBLEM

Pain A class that constructs its own collaborators ( [Link] = new UserRepository(); in the constructor) is welded to
that specific implementation. You cannot swap it for a mock in tests. You cannot swap it for a different impl in another
environment. The class decided too much.
Why DI Don't construct collaborators inside the class — receive them from outside (via the constructor). The class declares
what kind of collaborator it needs (an interface); someone else decides which concrete one to hand in. This separates "what
an object does" from "what objects it depends on" — and the second decision becomes configurable.

BEFORE AND AFTER

JAVA JAVA
// BEFORE: welded // AFTER: injected
class UserService { class UserService {
UserRepository repo final UserRepository repo;
= new UserRepository(); UserService(UserRepository r) { repo = r; }
} }

Notice: the second version doesn't say new anywhere. Who builds the UserRepository ? That is the question Spring's container answers.

T H R E E F L AV O R S O F D I

STYLE FORM VERDICT

Constructor Take dependencies as constructor args. Preferred. Final fields, fails fast if missing, easy to test.

Setter Setter methods inject dependencies after construction. Use only for optional dependencies.

Field @Autowired on a private field. Concise but bypasses immutability and complicates testing. Avoid.

DIAGRAM

Spring Container

UserController UserService UserRepository


needs: UserService needs: UserRepository needs: DataSource

The container walks the graph, builds leaves first, and injects upward.

PRODUCTION REALITY

▶ In Spring Boot — what the annotations actually mean


At startup, Spring scans @Component (and its specialisations @Service , @Repository , @Controller ) and registers each
as a bean. It then walks each bean's constructor, finds matching beans for each parameter, and instantiates them in the correct order.
The result is one ready-to-use object graph held in the ApplicationContext. Your code never says new — Spring does. That's it.
That's the magic.

Java → Spring Boot Bridge 41


30 Inversion of Control & the Container ~2 min

THE SHIFT

Traditional control flow: your code is in charge — it calls libraries when it needs them, builds its own dependencies, drives its
own lifecycle.

Inversion of Control: the framework is in charge. It builds objects, calls your code at the right moments, manages lifecycle.
Your code is called, it doesn't call. This is sometimes summarized as the Hollywood Principle: "don't call us, we'll call you."
DI is one technique that implements IoC — specifically for the "who constructs whom" question. The Spring container (a.k.a.
ApplicationContext ) is the runtime that does it.

B E A N L I F E C Y C L E I N O N E B R E AT H

1. Application starts → Spring reads configuration (annotations + properties).


2. Component scan: finds all @Component classes on the classpath.
3. For each, figures out constructor dependencies; topologically orders the graph.
4. Instantiates beans bottom-up, injecting collaborators.
5. Calls any @PostConstruct methods (initialization hooks).
6. Application is ready. Requests start flowing. Each request uses the already-built beans.
7. On shutdown, calls any @PreDestroy methods, closes resources.

B E A N S C O P E S ( T H E T W O Y O U ' L L A C T U A L LY U S E )

singleton (default) One instance per container. Shared across all threads. Must be stateless or thread-safe.

prototype New instance every time the bean is requested. Use sparingly.

Web-specific scopes ( request , session ) also exist but are niche.

W H E R E Y O U S TA N D N O W

▶ You can read Spring Boot code


You now have every prerequisite. @RestController is an annotation (Topic 20). The controller's dependencies are constructor-
injected (Topic 9). The repository is an interface (Topic 6). It returns Optional<User> (Topic 19). The service might call
.stream().map(...).toList() (Topic 18). Spring loads beans via reflection (Topic 21). Errors become 404s through a
@RestControllerAdvice (Topic 16). Database access is JDBC underneath (Topic 25). And the container ties it all together (this
topic). None of it is magic. It is all assembled Java.

Java → Spring Boot Bridge 42


Cheatsheet I — OOP & Type System
Print this. Stick it next to your keyboard. Glance, don't re-read.

Class keywords — the four pillars Modifiers — who sees what

class — blueprint for objects Modifier Class Pkg Sub World


extends — inherit from one class
public ✓ ✓ ✓ ✓
implements — promise interface methods
abstract — incomplete class, must extend protected ✓ ✓ ✓ ×

interface — pure contract, no state default ✓ ✓ × ×


record — immutable data carrier (Java 14+)
private ✓ × × ×
sealed — restrict who can extend (Java 17+)
Rule of thumb: start private, open only what callers truly need.

final — three meanings static — belongs to class, not instance

final int x = 10; // can't reassign class Counter {


final class String {} // can't extend static int total = 0; // shared
final void run() {} // can't override int id; // per-object
Counter() { id = ++total; }
}

Access without new: [Link]

Object methods you'll override Generics — bounded wildcards (PECS)

// Override BOTH or NEITHER // Producer Extends


@Override public boolean equals(Object o) {...} void readFrom(List<? extends Number> src);
@Override public int hashCode() {...}
@Override public String toString() {...} // Consumer Super
void writeTo(List<? super Integer> dst);
Or use record — gets all three for free.
PECS = Producer-Extends, Consumer-Super.

Collections — pick the right one Stream — intermediate vs terminal

Need Use Intermediate (lazy, return Stream):


filter map flatMap sorted distinct limit peek
Indexed list ArrayList
Terminal (trigger execution):
Frequent add/remove ends ArrayDeque
collect forEach reduce count findFirst anyMatch
No duplicates, no order HashSet
toList
Sorted unique TreeSet

Key→value lookup HashMap

Preserve insert order LinkedHashMap

Thread-safe map ConcurrentHashMap

Java → Spring Boot Bridge 43


Cheatsheet II — Concurrency, JDBC & Spring
The last mile before Spring Boot. Memorise the shapes; the framework will fill the rest.

Exception hierarchy try-with-resources — auto-close

Throwable try (var conn = [Link]();


├ Error // JVM-level, don't catch var ps = [Link](sql)) {
└ Exception [Link]();
├ RuntimeException // unchecked } // close() called automatically
│ ├ NullPointer
│ ├ Illegal* Resource must implement AutoCloseable.
│ └ ArithmeticException
└ (others) // checked
├ IOException
└ SQLException

Checked = compiler forces try/catch or throws. Unchecked = your bug.

Thread starters synchronized vs volatile

// Don't do this in prod — use a pool synchronized volatile


new Thread(() -> work()).start();
Atomic Yes No

// Do this Visible Yes Yes


var pool = [Link](8);
Blocks Yes No
[Link](() -> work());
[Link](); Use for Read+write Flags

JDBC — 5 steps, every time JPA — annotation starter pack

// 1. Get connection (from pool) @Entity // row of a table


Connection c = [Link](); @Table(name = "users")
// 2. Prepare statement class User {
var ps = [Link]("SELECT * FROM u WHERE id=?"); @Id @GeneratedValue Long id;
// 3. Bind params @Column(nullable=false) String email;
[Link](1, 42); @Enumerated([Link]) Role role;
// 4. Execute & read @OneToMany(mappedBy="user")
ResultSet rs = [Link](); List<Order> orders;
while ([Link]()) { ... } }
// 5. Close (try-with-resources does it)

Spring stereotypes Dependency Injection — the right way

Annotation For @Service


class OrderService {
@Component Generic bean
private final PaymentGateway pay;
@Service Business logic // constructor injection — preferred
OrderService(PaymentGateway pay) {
@Repository Data access
[Link] = pay;
@Controller Web request }
}
@RestController JSON API
Why constructor? Final fields, no nulls, easy to unit-test, no Spring
@Configuration Bean factory
needed in tests.
All four are @Component underneath — different names tell Spring (and humans)
the role.

Java → Spring Boot Bridge 44


You are ready.
Here's what to do next.

The path from here → Spring Boot


1. Install Java 17+, Maven, and an IDE (IntelliJ IDEA Community is free).
2. Go to [Link]. Pick: Maven, Java 17, Spring Web, Spring Data JPA, H2.
3. Build one tiny REST endpoint: @RestController + @GetMapping("/hello") .
4. Add an @Entity and a JpaRepository . Read & write rows.
5. Throw an exception. Handle it with @RestControllerAdvice .
6. Write one unit test. Then one @SpringBootTest .

A note on learning frameworks


Spring Boot will feel like magic at first — annotations everywhere, things "just working." Resist the urge to memorise
annotations. Every annotation in this book maps to something you now understand: DI, reflection, generics, interfaces,
exceptions, JDBC. When something feels magical, ask: which Java mechanism is this hiding? That question is the entire
mental model.

— end —

Built as a bridge, not a wall. Cross it.

Java → Spring Boot Bridge 45

You might also like