Spring Boot - Part 1 (Annotations, Dependency Injection, Layered Architecture)

  

Spring Boot is a framework built on top of the Spring Framework that makes it easy to build production ready Java applications. Before Spring Boot, setting up a Spring application required a lot of manual configuration through XML files and boilerplate code. Spring Boot eliminates all of that by providing smart defaults and auto configuration — you just add annotations and everything works automatically.

Spring is the original framework — very powerful but required a lot of manual configuration. You had to write XML config files, manually set up servers, and configure everything yourself.

Spring Boot is built on top of Spring and hides all that complexity. It comes with sensible defaults, an embedded server, and auto configuration so you can focus on writing business logic instead of configuration.

Think of it like :

  • Spring — raw ingredients, you cook everything from scratch
  • Spring Boot — a meal kit, everything is pre-prepared, you just put it together

What Spring Boot gives you

Auto Configuration — Spring Boot looks at what dependencies you have added and automatically configures everything for you. Add a MySQL dependency and Spring Boot sets up the database connection automatically. Add Spring Security and it automatically secures all your endpoints.

Embedded Server — Spring Boot comes with an embedded Tomcat server built in. You do not need to install, configure, or deploy to a separate server. Just run the app and it is immediately live on port 8080.

Dependency Management — Spring Boot manages all library versions for you through Maven or Gradle. You just say "I need Spring Web" and Spring Boot downloads it and ensures all versions are compatible with each other.

Production Ready Features — Spring Boot comes with built in health checks, metrics, and monitoring tools out of the box through Spring Actuator.

Spring Ecosystem — Spring Boot gives you access to the entire Spring ecosystem — Spring Security for authentication, Spring Data JPA for databases, Spring Mail for emails, Spring Cache for caching and much more. All of these are first class citizens and integrate seamlessly.


Spring Boot Installation

Before starting make sure you have the following installed :


# Check Java version — need Java 17 or higher
java -version

# Check Maven
mvn -version

Setting up a Spring Boot project from scratch manually is painful. You would have to : 

  • - Manually create the folder structure 
  • - Write the entire `pom.xml` from scratch 
  • - Add all the right dependencies with correct versions 
  • - Create the main application class 
  • - Configure everything yourself

Spring Initializr at https://start.spring.io is an official tool by the Spring team that solves this. You just fill in your project details, select the dependencies you need, click Generate, and it gives you a perfectly structured zip file with everything set up correctly and ready to open in your IDE. Think of it exactly like create-react-app for React. Instead of manually setting up Webpack, Babel, folder structure and everything else, one tool generates the entire project for you in seconds.

What you fill in on Spring Initializer

  • Project — choose Maven or Gradle as your build tool. Maven is recommended as most tutorials use it.
  • Language — choose Java, Kotlin, or Groovy. Always choose Java.
  • Spring Boot Version — choose the latest stable 3.3.x version. Avoid 4.x as it is too new and most resources online are written for 3.x.
  • Group — your base package name, usually your name or company in reverse domain format like com.deepesh. This becomes the root package for all your code.
  • Artifact — your project name like myapp. This becomes the name of your generated zip file and jar file.
  • Packaging — choose Jar. This packages your app into a single runnable file.
  • Java Version — choose 17 or higher.
  • Dependencies — the libraries you want Spring Boot to include. You search and add them on the right side of the page. Common ones are Spring Web, Spring Data JPA, MySQL Driver etc.

Once your project is generated from Spring Initializr and opened in VS Code, you use Maven commands from the terminal to run, build, and manage your Spring Boot application.

  • mvn spring-boot:run — the most common command you will use during development. It compiles your code and starts the Spring Boot application. Your app will be live at http://localhost:8080. Use this every time you want to run your app locally.
  • mvn clean — deletes all compiled files and build output from the target/ folder. Use this when you want a fresh start or when your build is behaving unexpectedly.
  • mvn clean package — cleans first, then compiles your code, runs all tests, and packages everything into a single runnable .jar file inside the target/ folder. Use this when you want to build your app for deployment.
  • mvn clean package -DskipTests — same as above but skips running tests. Use this when you want to build quickly without waiting for tests to finish.
  • java -jar target/myapp-0.0.1-SNAPSHOT.jar — runs the packaged .jar file directly. This is how your app runs in production on a server. You first build with mvn clean package and then run the generated .jar file with this command.
  • mvn clean install — cleans, compiles, tests, packages, and installs the .jar into your local Maven cache (~/.m2 folder). Use this when other local projects depend on this project.

NOTE : During development you will almost always just use mvn spring-boot:run. The other commands are mainly used when building for deployment or troubleshooting build issues.

How Spring Boot works internally

When you run a Spring Boot application this is what happens behind the scenes :


1] main() method runs
2] SpringApplication.run() starts the Spring Container (Application Context)
3] Spring scans all classes in your package for annotations
4] Spring creates and manages all annotated classes as Beans
5] Spring wires all dependencies together automatically
6] Auto configuration kicks in based on your dependencies
7] Embedded Tomcat server starts on port 8080
8] App is ready to receive HTTP requests

The Spring Container also called Application Context is the heart of Spring Boot. It is a registry that holds all the objects Spring creates and manages. These objects are called Beans. Spring creates them once, stores them in the container, and reuses them wherever needed.

Every Spring Boot application starts from a single main class annotated with @SpringBootApplication :


@SpringBootApplication
public class MyAppApplication {

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

}
```

`@SpringBootApplication` does 3 things :
- Marks this as a Spring Boot application
- Enables auto configuration
- Tells Spring to scan all classes in this package and subpackages for annotations

Project Structure

Spring Boot does not enforce a folder structure but the entire community follows this convention called Layered Architecture. Each layer has one responsibility and only talks to the layer directly below it. Controller never touches the database. Repository never has business logic. This separation is called Separation of Concerns and makes your code clean, maintainable, and testable.


src/main/java/com/deepesh/myapp/
├── MyAppApplication.java ← entry point
├── controller/ ← handles HTTP requests
├── service/ ← business logic
├── repository/ ← database queries
├── model/ ← represents data and database tables
└── exception/ ← custom exceptions

src/main/resources/
└── application.properties ← configuration file

pom.xml ← Maven dependencies
```

---

## How a Request flows through Spring Boot
```
Client (React / Postman / Browser)
Controller ← receives HTTP request, calls service
Service ← business logic, calls repository
Repository ← talks to database
Database ← returns data
Repository ← passes data up to service
Service ← passes data up to controller
Controller ← sends JSON response back to client
Client


Build Tools

Spring Boot uses Maven or Gradle to manage dependencies and build the project. Both tools download libraries from Maven Central — the central online repository for Java libraries. Think of it like npm registry but for Java.

Maven uses a pom.xml config file with XML syntax. Most widely used in enterprise Java and Spring Boot tutorials. Gradle uses a build.gradle config file with Groovy/Kotlin syntax. Cleaner and faster than Maven.

Routes — how URLs are defined

Unlike Express.js where routes are defined in a single file, Spring Boot routes are defined directly on methods using annotations. Spring Boot scans all classes at startup and automatically registers every route it finds.


@RestController
@RequestMapping("/users")
public class UserController {

@GetMapping // GET /users
public List<User> getAllUsers() { }

@GetMapping("/{id}") // GET /users/{id}
public User getUser() { }

@PostMapping // POST /users
public User createUser() { }

}

application.properties

This is the main configuration file for your Spring Boot app. It lives in src/main/resources/ and is where you configure everything — server port, database connection, JWT secrets, email settings etc.


# Server
server.port=8080

# Database
spring.datasource.url=jdbc:mysql://localhost:3306/mydb
spring.datasource.username=root
spring.datasource.password=password

# JPA
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true

Quick Start — minimal Spring Boot REST API

This is all you need to get a REST API running :


package com.deepesh.myapp.model;

public class User {

private int id;
private String name;
private String email;

// Constructor
public User(int id, String name, String email) {
this.id = id;
this.name = name;
this.email = email;
}

// Getters
public int getId() { return id; }
public String getName() { return name; }
public String getEmail() { return email; }

}




// 1] Entry point
@SpringBootApplication
public class MyAppApplication {
public static void main(String[] args) {
SpringApplication.run(MyAppApplication.class, args);
}
}

// 2] Model
public class User {
private int id;
private String name;
private String email;
// constructors, getters, setters
}

// 3] Controller
@RestController
@RequestMapping("/users")
public class UserController {

@Autowired
private UserService userService;

@GetMapping
public List<User> getAllUsers() {
return userService.getAllUsers();
}

@GetMapping("/{id}")
public User getUser(@PathVariable int id) {
return userService.getUser(id);
}

@PostMapping
public User createUser(@RequestBody User user) {
return userService.createUser(user);
}

@DeleteMapping("/{id}")
public void deleteUser(@PathVariable int id) {
userService.deleteUser(id);
}

}

// 4] Service
@Service
public class UserService {

@Autowired
private UserRepository userRepository;

public List<User> getAllUsers() {
return userRepository.findAll();
}

public User getUser(int id) {
return userRepository.findById(id)
.orElseThrow(() -> new RuntimeException("User not found"));
}

public User createUser(User user) {
return userRepository.save(user);
}

public void deleteUser(int id) {
userRepository.deleteById(id);
}

}

// 5] Repository
@Repository
public interface UserRepository extends JpaRepository<User, Integer> {
// all basic CRUD methods are provided automatically by JpaRepository
}

NOTE : This is the complete pattern you will follow for every feature you build in Spring Boot. Controller → Service → Repository. Every time, without exception.

-----------------------------------------------------------------------------------------------------------------------------

Spring Beans

A Bean is simply a Java object that is created and managed by Spring instead of you. That is it — nothing fancy, nothing special. The same Java object you have always worked with, except Spring creates it, stores it, and manages its entire lifecycle instead of you doing it manually.


// You create it manually
UserService userService = new UserService();

// You use it
userService.getUser(1);

In Spring Boot you never create objects manually. You just mark a class with an annotation and Spring creates the object for you at startup, stores it, and gives it to you whenever you need it :

    // Mark the class — Spring creates the object automatically
@Service
public class UserService { }

// Ask for it — Spring gives it to you
@Autowired
private UserService userService;

Beans are Singletons

By default Spring creates only one instance of each Bean and reuses it everywhere in your app. No matter how many classes use UserService, there is always only one UserService object in the entire application.


// Both controllers get the exact same UserService object
@RestController
public class UserController {
@Autowired
private UserService userService; // same instance
}

@RestController
public class AdminController {
@Autowired
private UserService userService; // same instance
}


Spring Boot Lifecycle — Beans, Container & Everything


Step 1 — You run the app

When you run mvn spring-boot:run the main() method executes and SpringApplication.run() kicks everything off. This one line starts the entire Spring Boot machinery — creating the container, scanning classes, wiring dependencies, and starting the server. Everything that happens next is triggered by this single line.

Step 2 — Application Context is Created

The very first thing Spring does is create the Application Context. This is the heart of Spring Boot — a container that will hold and manage all your Beans throughout the entire lifetime of your application. Think of it like an empty warehouse that is about to be stocked with objects. Every Bean Spring creates will be stored here and retrieved from here whenever needed.

Step 3 — Component Scanning

Spring now scans every class inside your root package and all subpackages looking for annotations like @RestController@Service@Repository, and @Component. This is why your folder structure matters — all your classes must be inside the same package as your main class otherwise Spring will not find them. This scanning process is what makes Spring Boot feel magical — you never manually register anything, Spring finds everything on its own.

Step 4 — Bean Creation

For every class Spring finds with those annotations, it creates an object and stores it in the Application Context. These objects are your Beans. Spring created them, not you. You never called new UserService() or new UserRepository() — Spring did that automatically during this step. The Application Context now holds one instance of every annotated class in your project.

Step 5 — Dependency Injection

Spring now looks at all the Beans it just created and checks if any of them have @Autowired fields. If a Bean needs another Bean to work, Spring finds the right Bean from the Application Context and injects it automatically. After this step every Bean has everything it needs — a Controller has its Service, a Service has its Repository. Everything is wired together without you writing a single line of wiring code.

Step 6 — Auto Configuration

Spring Boot now looks at what dependencies you added in pom.xml and automatically configures them for you. If you added a MySQL Driver and Spring Data JPA, Spring automatically sets up the database connection. If you added Spring Security, Spring automatically secures all your endpoints. If you added Spring Web, Spring sets up an embedded Tomcat server. You never write any of this configuration manually — Spring does it all based on what dependencies are present in your project.

Step 7 — Embedded Tomcat Starts

Spring Boot starts the embedded Tomcat server on port 8080. It registers all the routes it found from @GetMapping@PostMapping@PutMapping@DeleteMapping etc. and starts listening for incoming HTTP requests.

Step 8 — App is Ready

Your app is now fully running and ready to handle requests. Every incoming HTTP request flows through the wired Beans — from Controller to Service to Repository and back — with Spring managing all the objects behind the scenes. You just write the business logic inside each layer and Spring handles the rest.


mvn spring-boot:run
main() runs → SpringApplication.run()
Application Context created
Component Scanning — finds @Service, @Repository, @RestController etc.
Bean Creation — creates objects and stores in container
Dependency Injection — injects @Autowired fields
Auto Configuration — configures database, security etc.
Tomcat starts on port 8080
App is ready to handle requests

NOTE : The Application Context lives for the entire lifetime of your application. It is created when the app starts and destroyed when the app shuts down. All Beans inside it follow the same lifecycle.

NOTE : This entire process happens in just a few seconds when you run your app. Spring Boot is doing an enormous amount of work behind the scenes so that you do not have to.

-----------------------------------------------------------------------------------------------------------------------------

Spring Annotations

Annotations are the backbone of Spring Boot. Instead of writing hundreds of lines of XML configuration, you just put an annotation on a class or method and Spring handles everything automatically. Spring Boot annotations are grouped into categories based on what they do :

  • Application Annotations — bootstrap and start the application.
  • Layer Annotations — tell Spring which layer a class belongs to and to manage it as a Bean.
  • REST API Annotations — define URL routes and map HTTP methods to Java methods.
  • Request Handling Annotations — extract data from incoming HTTP requests like URL params, body, and headers.
  • Dependency Injection Annotations — wire Beans together automatically without manually creating objects.
  • Configuration Annotations — configure the application and define Beans manually.
  • Database Annotations — map Java classes and fields to database tables and columns.
  • Validation Annotations — validate incoming request data automatically before it reaches your business logic.
  • Utility Annotations — add extra functionality like transactions, scheduling, async execution, and logging.


1] Application Annotation

These are used on the main class to bootstrap and start the application. You will only ever use one annotation here. @SpringBootApplication — the most important annotation in any Spring Boot project. Put it on your main class. It does 3 things in one :

  • Marks this as a Spring Boot application
  • Enables auto configuration
  • Tells Spring to scan all classes in this package and subpackages for annotations like @RestController@Service@Repository etc.

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

NOTE : @SpringBootApplication is actually 3 annotations combined into one — @SpringBootConfiguration@EnableAutoConfiguration, and @ComponentScan. You will never need to use these 3 separately — @SpringBootApplication covers all of them.

NOTE : The package where your main class lives becomes the root package. Spring only scans classes inside this package and its subpackages. This is why all your folders — controller, service, repository etc. — must be inside the same package as your main class.


2] Layer Annotations

These tell Spring which layer a class belongs to and to manage it as a Bean. When Spring scans your project and finds these annotations it automatically creates an object of that class and stores it in the Application Context.

All 4 of these do the same thing technically — they all make a class a Spring Bean. The difference is that they communicate the role of that class to Spring and to other developers reading your code.

  • @Component — the generic parent annotation. All the other 3 below are specializations of @Component. Use this when your class does not fit into any specific layer — for example a utility class or a helper class.
  • @RestController — marks a class as a REST API controller. This is the entry point of every HTTP request. It handles incoming requests and automatically converts your Java return values into JSON responses. It is actually two annotations combined — @Controller and @ResponseBody — merged into one for convenience.
  • @Service — marks a class as a service. This is where all your business logic lives — calculations, decisions, validations, calling external APIs, sending emails etc. The Controller calls the Service.
  • @Repository — marks a class as a repository. This is where all your database queries live. The Service calls the Repository. Spring also adds an extra feature to this annotation — it automatically catches database specific exceptions and converts them into Spring exceptions so your error handling stays consistent regardless of which database you use.


@Component // generic Bean — utility or helper classes
public class EmailHelper { }

@RestController // controller layer — handles HTTP requests
public class UserController { }

@Service // service layer — business logic
public class UserService { }

@Repository // repository layer — database queries
public class UserRepository { }

Why not just use @Component everywhere?

Technically you could mark everything with @Component and Spring Boot would still work perfectly. But using the specific annotations has 2 benefits :

  • First it communicates intent — when another developer sees @Service they immediately know this class contains business logic without reading a single line of code inside it.
  • Second it enables layer specific features — @Repository automatically translates database exceptions, @RestController automatically converts return values to JSON. These features only work with the specific annotations.

NOTE : These 4 annotations are what make the entire Spring Boot ecosystem work. Without them Spring would not know which classes to manage and nothing would be injected or wired together. Every class in your project that needs to be managed by Spring must have one of these annotations.


3] REST API Annotations

These annotations go on methods inside a @RestController class to define your API routes. They tell Spring which HTTP method and which URL path a method should handle.

  • @RequestMapping — sets a base URL for all endpoints in a controller class. Instead of repeating /users on every single method you write it once on the class and all methods inside automatically inherit it.
  • @GetMapping — maps a method to handle HTTP GET requests. Used for fetching data. When a client wants to read something they send a GET request.
  • @PostMapping — maps a method to handle HTTP POST requests. Used for creating new data. When a client wants to create something they send a POST request with the data in the request body.
  • @PutMapping — maps a method to handle HTTP PUT requests. Used for updating existing data completely. The client sends the entire updated object.
  • @PatchMapping — maps a method to handle HTTP PATCH requests. Used for partial updates. Unlike PUT which replaces the entire object, PATCH only updates the specific fields that were sent.
  • @DeleteMapping — maps a method to handle HTTP DELETE requests. Used for deleting data.


@RestController
@RequestMapping("/users") // base URL — all methods below inherit /users
public class UserController {

@GetMapping // GET /users — fetch all users
public List<User> getAll() { }

@GetMapping("/{id}") // GET /users/1 — fetch one user
public User getOne() { }

@PostMapping // POST /users — create a user
public User create() { }

@PutMapping("/{id}") // PUT /users/1 — update entire user
public User update() { }

@PatchMapping("/{id}") // PATCH /users/1 — update specific fields
public User partialUpdate() { }

@DeleteMapping("/{id}") // DELETE /users/1 — delete a user
public void delete() { }

}

NOTE : The combination of HTTP method and URL path must be unique in your entire application. You cannot have two @GetMapping("/users") methods — Spring would not know which one to call.

Before @GetMapping@PostMapping etc. existed, developers used @RequestMapping for everything and had to specify the HTTP method manually :


// OLD WAY — using @RequestMapping for everything
@RestController
public class UserController {

@RequestMapping(value = "/users", method = RequestMethod.GET)
public List<User> getAll() { }

@RequestMapping(value = "/users", method = RequestMethod.POST)
public User create() { }

@RequestMapping(value = "/users/{id}", method = RequestMethod.PUT)
public User update() { }

@RequestMapping(value = "/users/{id}", method = RequestMethod.DELETE)
public void delete() { }

}

Both ways do exactly the same thing. The new way is just cleaner and easier to read. You will always use the shortcut annotations in modern Spring Boot. You will only see @RequestMapping with a method specified in very old codebases.


4] Request Handling Annotations

These annotations extract data from incoming HTTP requests. When a client sends a request it can contain data in different places — in the URL path, in the URL query parameters, in the request body as JSON, or in the request headers. These annotations tell Spring where to look and how to extract that data and pass it to your method.

  • @RequestBody — extracts the JSON from the request body and automatically converts it into a Java object. Used in POST and PUT requests where the client sends data. Spring uses a library called Jackson behind the scenes to do this JSON to Java conversion automatically.
  • @PathVariable — extracts a dynamic value from the URL path. For example in /users/1 the 1 is a path variable. You define it in the URL using curly braces like /{id} and Spring automatically extracts it and passes it to your method.
  • @RequestParam — extracts a query parameter from the URL. Query parameters come after the ? in a URL. For example in /users?page=1&size=10 the page and size are query parameters. Commonly used for filtering, searching, and pagination.
  • @RequestHeader — extracts a value from the HTTP request header. Headers contain metadata about the request. The most common use case is extracting the JWT token from the Authorization header to authenticate the user.


@RestController
@RequestMapping("/users")
public class UserController {

// @RequestBody — client sends JSON, Spring converts it to User object
// POST /users
// Body : { "name": "Deepesh", "email": "deepesh@gmail.com" }
@PostMapping
public User createUser(@RequestBody User user) {
System.out.println(user.getName()); // Deepesh
return user;
}

// @PathVariable — extracts {id} from the URL path
// GET /users/1
@GetMapping("/{id}")
public String getUser(@PathVariable int id) {
System.out.println(id); // 1
return "User " + id;
}

// @RequestParam — extracts query parameters from URL
// GET /users?page=1&size=10
@GetMapping
public String getUsers(@RequestParam int page,
@RequestParam int size) {
System.out.println(page); // 1
System.out.println(size); // 10
return "Page " + page + " Size " + size;
}

// @RequestHeader — extracts value from request header
// Header : Authorization: Bearer eyJhbGci...
@GetMapping("/profile")
public String getProfile(@RequestHeader("Authorization") String token) {
System.out.println(token); // Bearer eyJhbGci...
return "Profile";
}

}

NOTE : Jackson library is what converts JSON to Java objects for @RequestBody and Java objects back to JSON for @RestController responses. It does this automatically — you never call Jackson directly. This is part of Spring Boot's auto configuration.


5] Dependency Injection Annotations

These annotations wire your Beans together automatically. Instead of manually creating objects and passing them around, you declare what you need and Spring finds the right Bean from the Application Context and injects it for you.

  • @Autowired — the most commonly used DI annotation. Put it on a field, constructor, or setter and Spring will automatically find the matching Bean from the Application Context and inject it. This is how every layer gets access to the layer below it — Controller gets its Service, Service gets its Repository.
  • @Qualifier — used when you have multiple Beans of the same type and Spring does not know which one to inject. You use @Qualifier to specify exactly which Bean you want by name.
  • @Primary — when you have multiple Beans of the same type, marking one with @Primary tells Spring to always inject this one by default unless @Qualifier specifies otherwise.


// Basic @Autowired — most common usage
@RestController
public class UserController {

@Autowired
private UserService userService; // Spring finds UserService Bean and injects it

@GetMapping("/users")
public List<User> getUsers() {
return userService.getAllUsers();
}

}

@Service
public class UserService {

@Autowired
private UserRepository userRepository; // Spring finds UserRepository Bean and injects it

public List<User> getAllUsers() {
return userRepository.findAll();
}

}

Below are 3 different ways to use @Autowired in Spring Boot Application :

  • 1] Field Injection — the simplest way. You put @Autowired directly on the field and Spring injects the Bean automatically. This is the most commonly seen approach in tutorials because it is the least amount of code. However it is not recommended for real projects because it hides dependencies and makes testing harder.
  • 2] Constructor Injection — the recommended approach in real projects. Instead of putting @Autowired on a field, you declare the dependency as a constructor parameter. Spring sees the constructor and automatically injects the right Bean when creating the object. If there is only one constructor you do not even need @Autowired — Spring handles it automatically. This approach makes dependencies explicit and makes testing much easier because you can pass any object through the constructor without needing Spring at all.
  • 3] Setter Injection — rarely used in practice. You put @Autowired on a setter method and Spring calls that method to inject the Bean. This was more common in older Spring applications but has been largely replaced by constructor injection in modern Spring Boot projects.


// 1] Field Injection — simplest, most common in tutorials
// Just put @Autowired on the field
@RestController
public class UserController {

@Autowired
private UserService userService;

}

// 2] Constructor Injection — recommended best practice in real projects
// Spring automatically injects when there is only one constructor
// No need for @Autowired when there is only one constructor
@RestController
public class UserController {

private final UserService userService;

public UserController(UserService userService) {
this.userService = userService;
}

}

// 3] Setter Injection — rarely used
@RestController
public class UserController {

private UserService userService;

@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}

}

NOTE : Constructor injection is recommended over field injection in real projects because it makes dependencies explicit — you can clearly see what a class needs just by looking at its constructor. It also makes testing easier because you can pass mock objects directly through the constructor without needing Spring at all.

NOTE : In modern Spring Boot if a class has only one constructor, Spring automatically injects the dependencies without needing @Autowired on the constructor. This is why you will see many Spring Boot projects with no @Autowired at all — they use constructor injection and Spring handles it automatically.


6] Utility Annotations

These annotations add extra functionality to your classes and methods. They are not tied to any specific layer — you can use them anywhere in your application. 

  • @Transactional — wraps a method in a database transaction. A transaction means all the database operations inside that method either all succeed together or all fail together and get rolled back. For example if you are transferring money between two accounts — deducting from one and adding to another — if the second operation fails you do not want the first one to have happened. @Transactional handles this automatically.
  • @Scheduled — runs a method automatically on a schedule without any external trigger. You define when it should run using a cron expression or a fixed interval. Used for things like sending daily reports, cleaning up old data, syncing with external APIs etc.
  • @Async — runs a method in a background thread so it does not block the main thread. For example sending a welcome email after registration — you do not want the user to wait for the email to be sent before getting a response. With @Async the email is sent in the background and the user gets the response immediately.
  • @Slf4j — from the Lombok library. Automatically adds a logger to your class so you can log messages without writing any boilerplate logging code. Extremely commonly used in real projects.


@Service
public class UserService {

// @Transactional — if anything fails inside, all DB changes are rolled back
@Transactional
public void transferMoney(int fromId, int toId, double amount) {
// Step 1 — deduct from sender
accountRepository.deduct(fromId, amount);
// Step 2 — add to receiver
// if this fails, Step 1 is automatically rolled back
accountRepository.add(toId, amount);
}

// @Async — runs in background thread, does not block main thread
@Async
public void sendWelcomeEmail(String email) {
// this runs in background
// user gets response immediately without waiting for this
System.out.println("Sending welcome email to : " + email);
}

}



// @Scheduled — runs automatically on a schedule
// Need to add @EnableScheduling on main class to activate this
@Service
public class ReportService {

// Runs every day at midnight
@Scheduled(cron = "0 0 0 * * *")
public void sendDailyReport() {
System.out.println("Sending daily report...");
}

// Runs every 5 seconds
@Scheduled(fixedRate = 5000)
public void syncData() {
System.out.println("Syncing data...");
}

}


-----------------------------------------------------------------------------------------------------------------------------

Layers & Components

Every Spring Boot application follows a pattern called Layered Architecture. This means your application is divided into separate layers where each layer has one specific responsibility and only communicates with the layer directly below it. This is not a Spring Boot rule — it is a software engineering principle called Separation of Concerns. Spring Boot just makes it very easy to follow through its annotations.


Rules of Layered Architecture

These are not optional suggestions — they are rules every Spring Boot developer follows :

  • Controller only calls Service — never Repository directly.
  • Service only calls Repository — never another Service's Repository directly.
  • Repository only talks to Database — never contains business logic.
  • Model travels between all layers — it is the common language everyone speaks.


Client (React / Postman / Browser)
Controller Layer ← entry point, handles HTTP requests
Service Layer ← business logic
Repository Layer ← database queries
Database

Why Spring Boot does not create folders automatically ?

Spring Boot generates only the bare minimum when you create a project through Spring Initializr — the main class, application.properties, and pom.xml. That is it.

It does not create controller, service, repository, or model folders because Spring Boot has no idea what your application does. It does not know if you are building a hospital management system, a fitness tracker, or an e-commerce platform. Each application has different features, different entities, and different layers of complexity. Creating folders upfront would be making assumptions about your business logic which Spring Boot deliberately avoids.

What you get from Spring Initializr :


myapp/
├── src/
│ ├── main/
│ │ ├── java/com/deepesh/myapp/
│ │ │ └── MyappApplication.java ← only this is created
│ │ └── resources/
│ │ └── application.properties ← and this
│ └── test/
│ └── java/com/deepesh/myapp/
│ └── MyappApplicationTests.java
└── pom.xml ← and this

What you create manually

You create folders and files based on the features your application needs. For a User management feature you would create :


myapp/
├── src/
│ ├── main/
│ │ ├── java/com/deepesh/myapp/
│ │ │ ├── MyappApplication.java
│ │ │ ├── controller/
│ │ │ │ └── UserController.java ← you create this
│ │ │ ├── service/
│ │ │ │ └── UserService.java ← you create this
│ │ │ ├── repository/
│ │ │ │ └── UserRepository.java ← you create this
│ │ │ ├── model/
│ │ │ │ └── User.java ← you create this
│ │ │ └── exception/
│ │ │ └── UserNotFoundException.java ← you create this
│ │ └── resources/
│ │ └── application.properties
└── pom.xml

NOTE : Spring Boot does not enforce this folder structure — you could technically put everything in one file and it would still work. But every professional Spring Boot developer follows this convention. When you open any Spring Boot project in any company in the world it will follow this exact structure. Consistency is what makes codebases maintainable across teams.

NOTE : Notice that every feature follows the same pattern — Controller, Service, Repository, Model. This predictability is the biggest advantage of layered architecture. A new developer joining your team can immediately understand the codebase because everything follows the same structure.


Controller Layer — @RestController

The Controller is the entry point of your application. It is the only layer that talks to the outside world. Its only job is to receive HTTP requests, extract the data from them, pass that data to the Service, and send the response back to the client. The Controller should contain zero business logic — it should be as thin as possible. Receive, delegate, respond. Nothing else.

@RestController — marks a class as the Controller layer. Handles incoming HTTP requests and automatically converts Java return values to JSON responses. This is the only layer that talks to the outside world.


@RestController
@RequestMapping("/users")
public class UserController {

@Autowired
private UserService userService;

@GetMapping("/{id}")
public User getUser(@PathVariable int id) {
return userService.getUser(id); // just delegates to service
}

@PostMapping
public User createUser(@RequestBody User user) {
return userService.createUser(user); // just delegates to service
}

@DeleteMapping("/{id}")
public void deleteUser(@PathVariable int id) {
userService.deleteUser(id); // just delegates to service
}

}


Service Layer — @Service

The Service is the brain of your application. All your business logic lives here — calculations, decisions, validations, calling external APIs, sending emails, applying business rules etc. The Controller calls the Service and the Service calls the Repository. The Service should have no knowledge of HTTP — it should not know about requests, responses, or status codes. It just receives data, processes it, and returns a result.

@Service — marks a class as the Service layer. Spring Boot does not add any special behavior here beyond making it a Bean, but it communicates clearly that this class contains business logic.


@Service
public class UserService {

@Autowired
private UserRepository userRepository;

public User getUser(int id) {
return userRepository.findById(id)
.orElseThrow(() -> new RuntimeException("User not found"));
}

public User createUser(User user) {
if (user.getAge() < 18) {
throw new RuntimeException("User must be 18 or older");
}
if (userRepository.findByEmail(user.getEmail()).isPresent()) {
throw new RuntimeException("Email already exists");
}
User savedUser = userRepository.save(user);
System.out.println("Sending welcome email to : " + savedUser.getEmail());
return savedUser;
}

}


Repository Layer — @Repository

The Repository is the data access layer. Its only job is to talk to the database — fetching, saving, updating, and deleting data. The Service calls the Repository whenever it needs to interact with the database. In Spring Boot the Repository is almost always an interface that extends JpaRepository. Spring automatically provides all basic database operations like findAll()findById()save()deleteById() without you writing a single line of SQL.

@Repository — marks a class as the Repository layer. Spring adds an extra feature here — it automatically catches database specific exceptions and converts them into consistent Spring exceptions so your error handling works the same regardless of which database you use.


@Repository
public interface UserRepository extends JpaRepository<User, Integer> {

// Basic operations provided automatically by JpaRepository
// findAll(), findById(), save(), deleteById() etc.

// Custom queries — Spring generates SQL automatically from method names
Optional<User> findByEmail(String email);
List<User> findByCity(String city);
List<User> findByAgeGreaterThan(int age);

}


Model — The Data Class

The Model is not a layer — it is a plain Java class that represents your data and travels between all layers. It maps directly to a database table using JPA annotations.


@Entity
@Table(name = "users")
public class User {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;

private String name;
private String email;
private int age;

// Constructors, Getters, Setters

}

NOTE : @Component — the generic parent of all the above annotations. Use this for classes that do not belong to any specific layer — utility classes, helper classes, email senders etc.

-----------------------------------------------------------------------------------------------------------------------------

Dependency Injection

In any application classes need other classes to work. For example a UserController needs a UserService to handle business logic. A UserService needs a UserRepository to talk to the database. These are called dependencies — one class depends on another to do its job. The question is — how does a class GET its dependencies?

In normal Java you create them yourself using the new keyword. This works but creates a big problem — your classes become tightly coupled. Every class is responsible for creating and managing its own dependencies. As your app grows with hundreds of classes this becomes impossible to manage, hard to test, and difficult to change.


public class UserController {
UserService userService = new UserService(); // you manually create it ❌
}

public class UserController {
@Autowired
private UserRepository userRepository; // Spring injects UserRepository Bean here ✅
}

Dependency Injection solves this by flipping the responsibility. Instead of a class creating its own dependencies, something external creates them and injects them into the class. You just declare what you need and the injector handles the rest.

In Spring Boot that injector is the Spring Container (Application Context). Spring creates all your objects, manages them as Beans, and injects them wherever needed automatically. You never use the new keyword manually for Spring managed classes. You just declare what you need with @Autowired and Spring does the rest.

@Autowired

You already know that Spring scans your project at startup, finds classes marked with @Service@Repository@RestController etc., creates objects for them, and stores them in the Application Context as Beans. Dependency Injection is the next step — once all Beans are created, Spring looks for @Autowired annotations anywhere in your code and injects the right Bean automatically.


Spring sees @Autowired on UserService field
Spring looks inside Application Context
Finds a Bean of type UserService
Injects it automatically

@Autowired is the annotation that tells Spring — "I need this dependency, find the right Bean from the Application Context and inject it here." 


// ❌ Without @Autowired — userService is null, app crashes
@RestController
public class UserController {
private UserService userService; // null — Spring does not inject it
@GetMapping("/users")
public List<User> getUsers() {
return userService.getAllUsers(); // ❌ NullPointerException
}
}

NOTE : Without @Autowired Spring does not know you need that dependency and the field stays null. Calling any method on a null field crashes your app with a NullPointerException.

@Autowired is optional in one case

If a class has only one constructor Spring automatically injects dependencies through it without needing @Autowired. This is the basis of constructor injection which we will see next.


// @Autowired not needed — Spring injects automatically through the only constructor
@RestController
public class UserController {

private final UserService userService;

public UserController(UserService userService) {
this.userService = userService; // Spring injects UserService here automatically
}

}


3 Ways to Inject Dependencies

1] Field Injection — the simplest way. Put @Autowired directly on the field and Spring injects the Bean automatically. Most commonly seen in tutorials because it requires the least amount of code. However it is not recommended for real projects because the dependencies are hidden inside the class — you cannot tell what a class needs without reading all its fields. It also makes testing harder because you cannot inject mock objects without Spring.


@RestController
public class UserController {

@Autowired
private UserService userService; // Spring injects automatically

@GetMapping("/users")
public List<User> getUsers() {
return userService.getAllUsers();
}

}


2] Constructor Injection — the recommended best practice in real projects. You declare dependencies as constructor parameters and Spring automatically injects the right Beans when creating the object. Dependencies are explicit — anyone can see what a class needs just by looking at its constructor. If there is only one constructor you do not even need @Autowired. Also notice the field is final which means it cannot be changed after injection — making the class safer and more predictable.


@RestController
public class UserController {

private final UserService userService; // final — cannot be changed after injection

// No @Autowired needed — Spring injects automatically through the only constructor
public UserController(UserService userService) {
this.userService = userService;
}

@GetMapping("/users")
public List<User> getUsers() {
return userService.getAllUsers();
}

}


3] Setter Injection — put @Autowired on a setter method and Spring calls that method to inject the Bean. Was more common in older Spring applications but has been largely replaced by constructor injection in modern Spring Boot. Rarely seen in real projects today.


@RestController
public class UserController {

private UserService userService;

@Autowired
public void setUserService(UserService userService) {
this.userService = userService; // Spring calls this setter and injects UserService
}

@GetMapping("/users")
public List<User> getUsers() {
return userService.getAllUsers();
}

}

NOTE : In most real world Spring Boot projects you will see either field injection or constructor injection. Field injection for its simplicity during development, constructor injection for production quality code. Pick one style and stay consistent across your entire project.

Where @Autowired is Used

@Autowired is used in every layer of your application — not just controllers. The reason is simple — every layer depends on the layer below it to do its job. A Controller cannot work without a Service. A Service cannot work without a Repository. This chain of dependencies is what @Autowired wires together. Spring resolves this entire chain automatically at startup. Every @Autowired field gets the right Bean injected without you writing a single line of wiring code.

The Repository layer is the only layer that rarely uses @Autowired because it has no dependencies of its own — it is the last stop before the database. Every other layer will have at least one @Autowired field. In a real production Spring Boot app you will write @Autowired (or constructor injection) dozens of times across all layers. It is one of the most frequently used annotations in any Spring Boot project.


// Controller needs Service
@RestController
public class UserController {
@Autowired
private UserService userService;
}

// Service needs Repository
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
}

// Service might also need other Services
@Service
public class OrderService {
@Autowired
private UserService userService;
@Autowired
private ProductService productService;
@Autowired
private OrderRepository orderRepository;
}

@Primary and @Qualifier

Sometimes you have multiple Beans of the same type and Spring does not know which one to inject and throws error. For example two classes implementing the same interface. Spring will throw an error because it cannot decide which one to use. @Primary and @Qualifier solve this.

  • @Primary — marks one Bean as the default. When Spring finds multiple Beans of the same type it automatically injects the one marked with @Primary unless told otherwise.
  • @Qualifier — used when you want a specific Bean that is not the primary one. You specify the exact Bean name you want injected.


// The real use case — both implement the same interface
// Spring gets confused because both are of type NotificationService
interface NotificationService {
void send(String msg);
}

@Service
@Primary // use this one by default
public class EmailService implements NotificationService {
public void send(String msg) {
System.out.println("Email : " + msg);
}
}

@Service
public class SMSService implements NotificationService {
public void send(String msg) {
System.out.println("SMS : " + msg);
}
}

@RestController
public class UserController {

@Autowired
private NotificationService notificationService; // gets EmailService — it is @Primary

@Autowired
@Qualifier("SMSService") // specifically gets SMSService
private NotificationService smsService;

@GetMapping("/notify")
public String notify() {
notificationService.send("Welcome!"); // Email : Welcome!
smsService.send("Welcome!"); // SMS : Welcome!
return "Notifications sent";
}

}


-----------------------------------------------------------------------------------------------------------------------------

application.properties

application.properties is the main configuration file of your Spring Boot application. It lives in src/main/resources/ and is where you configure everything about your app — server port, database connection, JWT secrets, email settings, logging etc. Instead of hardcoding these values directly in your Java code, you put them here and inject them wherever needed.

All environment specific configuration lives in one place, separate from your code. In production you just swap the properties file or use environment variables and your code stays unchanged.

    # =============================================
# SERVER
# =============================================
server.port=8080 # port your app runs on
spring.application.name=myapp # name of your app

# =============================================
# DATABASE
# =============================================
spring.datasource.url=jdbc:mysql://localhost:3306/mydb
spring.datasource.username=root
spring.datasource.password=yourpassword
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# =============================================
# JPA
# =============================================
spring.jpa.hibernate.ddl-auto=update # auto create/update tables
spring.jpa.show-sql=true # print SQL queries in console
spring.jpa.properties.hibernate.format_sql=true # print SQL in readable format

# =============================================
# JWT
# =============================================
app.jwt.secret=mySecretKey
app.jwt.expiration=86400 # token expiry in seconds (24 hours)

# =============================================
# EMAIL
# =============================================
spring.mail.host=smtp.gmail.com
spring.mail.port=587
spring.mail.username=youremail@gmail.com
spring.mail.password=yourpassword

# =============================================
# LOGGING
# =============================================
logging.level.root=INFO # log level for entire app
logging.level.com.deepesh=DEBUG # log level for your package


Injecting Values into your Code — @Value

You can inject any value from application.properties directly into your Java class using @Value. The @Value uses ${} syntax with the exact key from application.properties. If the key does not exist and no default is provided Spring will throw an error at startup.


@Service
public class JwtService {

@Value("${app.jwt.secret}")
private String jwtSecret;

@Value("${app.jwt.expiration}")
private int jwtExpiration;

@Value("${spring.application.name}")
private String appName;

public void printConfig() {
System.out.println("App : " + appName);
System.out.println("Secret : " + jwtSecret);
System.out.println("Expiry : " + jwtExpiration);
}

}

You can provide a default value in case the property is not found in application.properties :


@Value("${app.jwt.secret:defaultSecret}") // uses defaultSecret if not found
private String jwtSecret;

@Value("${server.port:8080}") // uses 8080 if not found
private int port;
```

---

## Multiple Environments — application-dev.properties and application-prod.properties

In real projects you have different configurations for development and production.
    Spring Boot supports this through **profiles**. You create separate properties files
    for each environment :
```
src/main/resources/
├── application.properties ← shared config
├── application-dev.properties ← development config
└── application-prod.properties ← production config



Profiles

Profiles allow you to have different configurations for different environments (development, testing, production) without changing code. So you dont need to keep changing values in same "app.properties" file. Profiles are a way to have different configurations for different environments without changing your code.

Think of profiles as:

  • dev → Development environment (your laptop)

  • test → Testing environment (CI/CD pipeline)

  • prod → Production environment (live users)

NOTE : Spring Boot automatically detects and loads profile-specific property files based on the naming convention.


┌─────────────────────────────────────────────────────────────────┐
│ SPRING BOOT AUTO-DETECTION │
├─────────────────────────────────────────────────────────────────┤
│ │
│ You create these files:
│ │
│ src/main/resources/
│ ├── application.properties ← Base (always loaded) │
│ ├── application-dev.properties ← Auto-detected for dev │
│ ├── application-test.properties ← Auto-detected for test │
│ └── application-prod.properties ← Auto-detected for prod │
│ │
│ Spring Boot looks for files matching:
│ application-{active-profile}.properties │
│ │
│ If active profile = "dev" → automatically loads:
1. application.properties (base) │
2. application-dev.properties (overrides) │
│ │
└─────────────────────────────────────────────────────────────────┘


Step-by-Step Example

Step 1]: Create the Files

application.properties (base config)

properties
server.port=8080
app.name=MyApp

application-dev.properties (dev overrides)

properties
server.port=8081
logging.level.root=DEBUG

application-prod.properties (prod overrides)

properties
server.port=8080
logging.level.root=WARN

Step 2]: Activate a Profile

bash
# Development
java -jar myapp.jar --spring.profiles.active=dev

Step 3]: What Spring Boot Does Automatically

When you run with --spring.profiles.active=dev:

1. Spring Boot scans resources folder
2. Finds application.properties → loads it
3. Looks for application-dev.properties → finds it → loads it
4. Merges both files (dev properties override base)

Final configuration:
server.port=8081        ← from dev file (overrides base)
app.name=MyApp          ← from base file
logging.level.root=DEBUG ← from dev file



┌─────────────────────────────────────────────────────────────────┐
│ SPRING BOOT AUTO-MAGIC │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Application Startup:
│ │
1. Check if profile is active │
│ → Read from:
- command line: --spring.profiles.active=dev │
- environment: SPRING_PROFILES_ACTIVE=dev │
- application.properties: spring.profiles.active=dev │
│ │
2. If profile = "dev":
│ → Look for file: application-dev.properties │
│ → If found, load it │
│ │
3. Merge with base application.properties │
│ → Profile properties have HIGHER priority │
│ │
4. Use merged configuration for the entire app │
│ │
└─────────────────────────────────────────────────────────────────┘

What If Profile File Doesn't Exist?

If profile = "dev" but application-dev.properties doesn't exist:

→ Spring loads ONLY application.properties
→ No error, just uses base config
→ Good for optional profile configs


Priority Order (Higher to Lower)

PrioritySourceExample
1Command line--spring.profiles.active=dev
2Environment variableSPRING_PROFILES_ACTIVE=dev
3application-{profile}.propertiesapplication-dev.properties
4application.propertiesBase config

When Spring Boot has multiple sources of configuration, it needs to decide which one to use. The higher priority source overrides the lower priority ones.

Think of it as:

  • Higher priority = Closer to you, you can change it easily

  • Lower priority = Farther away, acts as default/fallback

-----------------------------------------------------------------------------------------------------------------------------




Comments

Popular posts from this blog

React Js + React-Redux (part-2)

React Js + CSS Styling + React Router (part-1)

ViteJS (Module Bundlers, Build Tools)