1. What is Spring Boot?
Spring Boot is a framework built on top of the Spring Framework that makes it extremely easy to build Java web applications and REST APIs. It follows the principle of "Convention over Configuration" — it auto-configures almost everything so you can focus on writing business logic instead of XML configuration files.
Spring Framework vs Spring Boot
| Feature | Spring Framework | Spring Boot |
|---|---|---|
| Configuration | Manual XML + Java config | Auto-configured |
| Server | Need external Tomcat | Embedded Tomcat (built-in) |
| Setup time | Hours of config | Minutes with Spring Initializr |
| Dependencies | Manually add each JAR | Starter packs (one dependency = everything) |
| XML files | Required (web.xml, beans.xml) | No XML needed |
| Learning curve | Steep | Beginner-friendly |
Spring Initializr
Go to start.spring.io to generate a Spring Boot project. Select your dependencies (Spring Web, Spring Data JPA, MySQL Driver, etc.), click Generate, and you get a ready-to-run project ZIP.
Key Features of Spring Boot
- Embedded Server — Tomcat runs inside your JAR. No external server deployment needed.
- Auto-Configuration — Detects dependencies and configures beans automatically.
- Starter Dependencies —
spring-boot-starter-webpulls in everything for web apps. - No XML — Everything done via annotations and properties files.
- Production-ready — Health checks, metrics, externalized config built in.
2. Project Structure
A standard Spring Boot project looks like this:
my-project/ ├── src/ │ ├── main/ │ │ ├── java/ // All your Java code goes here │ │ │ └── com/example/demo/ │ │ │ ├── DemoApplication.java // Main class (entry point) │ │ │ ├── controller/ // REST controllers │ │ │ ├── service/ // Business logic │ │ │ ├── repository/ // Database access │ │ │ └── model/ // Entity classes │ │ └── resources/ │ │ ├── application.properties // Configuration file │ │ ├── static/ // CSS, JS, images │ │ └── templates/ // HTML templates (Thymeleaf) │ └── test/ // Test files └── pom.xml // Maven dependencies
pom.xml — Maven Dependencies
The pom.xml file lists all libraries your project needs. Spring Boot uses "starter" dependencies — one starter pulls in multiple related JARs.
<!-- Key dependencies in pom.xml --> <dependencies> <!-- Spring Web — for building REST APIs --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Spring Data JPA — for database operations --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <!-- MySQL Driver — connects Java to MySQL --> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> </dependency> </dependencies>
spring-boot-starter-web— REST APIs, embedded Tomcatspring-boot-starter-data-jpa— JPA + Hibernate for databasespring-boot-starter-test— testing support
The Main Class — Entry Point
package com.example.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication // This ONE annotation does 3 things (see below) public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); // Starts the app } }
@SpringBootApplication = @Configuration + @EnableAutoConfiguration + @ComponentScan. This is asked very frequently. Know what each sub-annotation does.
3. Key Annotations
Annotations are the backbone of Spring Boot. Instead of XML config, you put @ annotations on classes and methods to tell Spring what to do. This is the most important section for exams.
Complete Annotation Reference Table
| Annotation | Where Used | What It Does |
|---|---|---|
@SpringBootApplication | Main class | Combines @Configuration + @EnableAutoConfiguration + @ComponentScan. Entry point. |
@RestController | Class | Makes the class a REST API controller. Every method returns data (JSON), not a view. |
@Controller | Class | Makes the class a web controller. Methods return view names (HTML pages). |
@RequestMapping("/path") | Class or Method | Maps a URL path to a class or method. Base path for all endpoints in the class. |
@GetMapping("/path") | Method | Handles HTTP GET requests (read/fetch data). |
@PostMapping("/path") | Method | Handles HTTP POST requests (create new data). |
@PutMapping("/path") | Method | Handles HTTP PUT requests (update existing data). |
@DeleteMapping("/path") | Method | Handles HTTP DELETE requests (remove data). |
@PathVariable | Method param | Extracts value from URL path. /emp/{id} → id = 5 |
@RequestParam | Method param | Extracts query parameter. /emp?name=John → name = "John" |
@RequestBody | Method param | Converts incoming JSON body to a Java object automatically. |
@Autowired | Field or Constructor | Injects a dependency automatically (Dependency Injection). |
@Service | Class | Marks class as a service layer component (business logic). |
@Repository | Class | Marks class as a data access layer component (database operations). |
@Component | Class | Generic Spring-managed bean. @Service and @Repository are specialized versions. |
@Entity | Class | Maps this Java class to a database table (JPA). |
@Table(name="tbl") | Class | Specifies the exact database table name for the entity. |
@Id | Field | Marks a field as the primary key of the table. |
@GeneratedValue | Field | Auto-generates the primary key value (auto-increment). |
@Column(name="col") | Field | Maps a field to a specific column name in the table. |
@RestController vs @Controller
@RestController // Returns DATA (JSON) — used for REST APIs public class ApiController { @GetMapping("/hello") public String hello() { return "Hello World"; // Returns this string as response body } } @Controller // Returns VIEW NAME (HTML page) — used for web pages public class WebController { @GetMapping("/home") public String home() { return "index"; // Looks for index.html template } }
@RestController = @Controller + @ResponseBody. The @ResponseBody part tells Spring to write the return value directly into the HTTP response body (as JSON or text) instead of looking for an HTML template.
@PathVariable vs @RequestParam vs @RequestBody
// @PathVariable — value is PART of the URL path // URL: GET /employees/5 @GetMapping("/employees/{id}") public Employee getById(@PathVariable Long id) { // id = 5 return service.findById(id); } // @RequestParam — value comes AFTER ? in URL // URL: GET /employees?department=IT @GetMapping("/employees") public List<Employee> getByDept(@RequestParam String department) { // department = "IT" return service.findByDept(department); } // @RequestBody — value comes from JSON body of the request // POST /employees with body: {"name":"John","department":"IT"} @PostMapping("/employees") public Employee create(@RequestBody Employee emp) { // JSON → Java object return service.save(emp); }
/emp/5). @RequestParam = value after ? (/emp?id=5). @RequestBody = JSON data in request body. They ask "which annotation to use when..." — know the difference.
4. REST Controller — Building APIs
A REST controller handles HTTP requests and returns data (usually JSON). Each method maps to an HTTP method (GET, POST, PUT, DELETE).
Simple GET Endpoint
@RestController // This class handles REST API requests @RequestMapping("/api") // Base URL: all endpoints start with /api public class HelloController { @GetMapping("/hello") // Handles GET /api/hello public String sayHello() { return "Hello from Spring Boot!"; // Response sent as plain text } }
GET with Path Variable
@GetMapping("/employees/{id}") // {id} is a placeholder in URL public Employee getEmployee(@PathVariable Long id) { return service.getEmployeeById(id); // Fetch employee by ID } // URL: GET /api/employees/5 → id = 5
POST — Create New Resource
@PostMapping("/employees") // Handles POST /api/employees public Employee createEmployee(@RequestBody Employee emp) { // @RequestBody converts JSON from request body → Employee object return service.saveEmployee(emp); // Save and return the new employee } // Send JSON: {"name": "John", "department": "IT", "salary": 50000}
PUT — Update Existing Resource
@PutMapping("/employees/{id}") // Handles PUT /api/employees/5 public Employee updateEmployee(@PathVariable Long id, @RequestBody Employee emp) { emp.setId(id); // Set the ID from URL path return service.saveEmployee(emp); // save() updates if ID exists }
DELETE — Remove Resource
@DeleteMapping("/employees/{id}") // Handles DELETE /api/employees/5 public String deleteEmployee(@PathVariable Long id) { service.deleteEmployee(id); // Delete employee with this ID return "Employee deleted successfully"; }
| HTTP Method | Annotation | Purpose | Example URL |
|---|---|---|---|
| GET | @GetMapping | Read / Fetch | GET /api/employees |
| POST | @PostMapping | Create | POST /api/employees |
| PUT | @PutMapping | Update | PUT /api/employees/5 |
| DELETE | @DeleteMapping | Delete | DELETE /api/employees/5 |
5. Layered Architecture
Spring Boot follows a layered architecture pattern. Each layer has a specific responsibility and talks only to the layer directly below it.
Client (Browser / Postman)
↓ HTTP Request
┌─────────────────────┐
│ Controller │ ← Handles HTTP requests, calls Service
│ (@RestController) │
└─────────┬───────────┘
↓
┌─────────────────────┐
│ Service │ ← Business logic, validation, rules
│ (@Service) │
└─────────┬───────────┘
↓
┌─────────────────────┐
│ Repository │ ← Database queries (CRUD operations)
│ (@Repository) │
└─────────┬───────────┘
↓
┌─────────────────────┐
│ Database │ ← MySQL, PostgreSQL, H2, etc.
└─────────────────────┘
Why Layers?
- Separation of Concerns — Each layer does ONE thing. Controller handles requests, Service handles logic, Repository handles database.
- Maintainability — Change database? Only Repository changes. Change business rules? Only Service changes.
- Testability — Each layer can be tested independently.
- Reusability — Multiple controllers can use the same service.
Each Layer's Responsibility
| Layer | Annotation | Responsibility |
|---|---|---|
| Controller | @RestController | Receive HTTP requests, validate input, call service, return response |
| Service | @Service | Business logic, calculations, validation rules, orchestration |
| Repository | @Repository | Database operations — save, find, update, delete |
| Model/Entity | @Entity | Represents a database table as a Java class |
6. JPA and Hibernate (ORM)
What is ORM?
ORM = Object-Relational Mapping. It maps Java objects to database tables automatically. Instead of writing SQL queries, you work with Java objects and JPA/Hibernate translates them to SQL behind the scenes.
| Java Concept | Database Concept |
|---|---|
| Class | Table |
| Object | Row |
| Field/Variable | Column |
Entity Class Example
An entity class is a regular Java class with JPA annotations that maps to a database table.
import jakarta.persistence.*; // JPA annotations @Entity // Tells JPA: this class = a database table @Table(name = "employees") // Table name in database (optional if class name = table name) public class Employee { @Id // This field is the PRIMARY KEY @GeneratedValue(strategy = GenerationType.IDENTITY) // Auto-increment private Long id; @Column(name = "emp_name") // Maps to column "emp_name" in table private String name; private String department; // No @Column? Column name = field name "department" private double salary; // Default constructor (REQUIRED by JPA) public Employee() {} // Parameterized constructor public Employee(String name, String department, double salary) { this.name = name; this.department = department; this.salary = salary; } // Getters and Setters (required for JPA and JSON conversion) public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getDepartment() { return department; } public void setDepartment(String dept) { this.department = dept; } public double getSalary() { return salary; } public void setSalary(double salary) { this.salary = salary; } }
public Employee() {}.
JpaRepository — Built-in Database Methods
By extending JpaRepository, you get a full set of database operations for FREE. No SQL needed.
import org.springframework.data.jpa.repository.JpaRepository; // JpaRepository<EntityClass, PrimaryKeyType> public interface EmployeeRepository extends JpaRepository<Employee, Long> { // That's it! You get all CRUD methods automatically: // findAll() → SELECT * FROM employees // findById(id) → SELECT * FROM employees WHERE id = ? // save(entity) → INSERT or UPDATE // deleteById(id) → DELETE FROM employees WHERE id = ? // count() → SELECT COUNT(*) FROM employees // existsById(id) → checks if row exists }
Built-in JpaRepository Methods
| Method | SQL Equivalent | Returns |
|---|---|---|
findAll() | SELECT * FROM table | List<Entity> |
findById(id) | SELECT * WHERE id = ? | Optional<Entity> |
save(entity) | INSERT or UPDATE | Saved Entity |
deleteById(id) | DELETE WHERE id = ? | void |
count() | SELECT COUNT(*) | long |
existsById(id) | SELECT EXISTS(...) | boolean |
Custom Queries with @Query
public interface EmployeeRepository extends JpaRepository<Employee, Long> { // Custom query method — Spring generates SQL from method name! List<Employee> findByDepartment(String department); // → SELECT * FROM employees WHERE department = ? List<Employee> findByNameContaining(String keyword); // → SELECT * FROM employees WHERE name LIKE '%keyword%' // Custom JPQL query (Java Persistence Query Language) @Query("SELECT e FROM Employee e WHERE e.salary > :minSalary") List<Employee> findHighEarners(@Param("minSalary") double minSalary); }
findByName→ WHERE name = ?findByDepartmentAndSalaryGreaterThan→ WHERE department = ? AND salary > ?findByNameContaining→ WHERE name LIKE '%?%'findBySalaryBetween→ WHERE salary BETWEEN ? AND ?
7. Complete CRUD Application
Here's a complete working CRUD app with all 4 layers. This is what you need to know for PRA and Sprint 3.
Layer 1: Entity — Employee.java
package com.example.demo.model; import jakarta.persistence.*; @Entity // This class maps to a database table @Table(name = "employees") // Table name: "employees" public class Employee { @Id // Primary key @GeneratedValue(strategy = GenerationType.IDENTITY) // Auto-increment private Long id; @Column(nullable = false) // Cannot be null in database private String name; private String department; private double salary; // Default constructor — REQUIRED by JPA public Employee() {} // Constructor with fields (no id — database generates it) public Employee(String name, String department, double salary) { this.name = name; this.department = department; this.salary = salary; } // --- Getters and Setters --- public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getDepartment() { return department; } public void setDepartment(String department) { this.department = department; } public double getSalary() { return salary; } public void setSalary(double salary) { this.salary = salary; } }
Layer 2: Repository — EmployeeRepository.java
package com.example.demo.repository; import com.example.demo.model.Employee; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository // Marks as data access layer public interface EmployeeRepository extends JpaRepository<Employee, Long> { // Employee = Entity class // Long = type of the primary key (id) // All CRUD methods are inherited automatically! // findAll(), findById(), save(), deleteById(), etc. }
Layer 3: Service — EmployeeService.java
package com.example.demo.service; import com.example.demo.model.Employee; import com.example.demo.repository.EmployeeRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; import java.util.Optional; @Service // Marks as service layer (business logic) public class EmployeeService { @Autowired // Inject repository automatically private EmployeeRepository repository; // GET ALL employees public List<Employee> getAllEmployees() { return repository.findAll(); // Returns all rows from table } // GET ONE employee by ID public Employee getEmployeeById(Long id) { Optional<Employee> emp = repository.findById(id); // May or may not exist if (emp.isPresent()) { return emp.get(); // Found — return employee } else { throw new RuntimeException("Employee not found with id: " + id); } } // CREATE a new employee (or UPDATE if id exists) public Employee saveEmployee(Employee emp) { return repository.save(emp); // save() handles both INSERT and UPDATE } // UPDATE an existing employee public Employee updateEmployee(Long id, Employee updatedEmp) { Employee existing = getEmployeeById(id); // Find existing record existing.setName(updatedEmp.getName()); // Update name existing.setDepartment(updatedEmp.getDepartment()); // Update department existing.setSalary(updatedEmp.getSalary()); // Update salary return repository.save(existing); // Save updated record } // DELETE an employee public void deleteEmployee(Long id) { repository.deleteById(id); // Delete row with this ID } }
Layer 4: Controller — EmployeeController.java
package com.example.demo.controller; import com.example.demo.model.Employee; import com.example.demo.service.EmployeeService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.List; @RestController // This handles REST API requests @RequestMapping("/api/employees") // Base URL for all endpoints public class EmployeeController { @Autowired // Inject service automatically private EmployeeService service; // GET /api/employees — Get all employees @GetMapping public List<Employee> getAll() { return service.getAllEmployees(); // Returns JSON array } // GET /api/employees/5 — Get one employee by ID @GetMapping("/{id}") public Employee getById(@PathVariable Long id) { return service.getEmployeeById(id); // Returns JSON object } // POST /api/employees — Create new employee @PostMapping public Employee create(@RequestBody Employee emp) { return service.saveEmployee(emp); // JSON body → Employee object } // PUT /api/employees/5 — Update employee @PutMapping("/{id}") public Employee update(@PathVariable Long id, @RequestBody Employee emp) { return service.updateEmployee(id, emp); // Update and return } // DELETE /api/employees/5 — Delete employee @DeleteMapping("/{id}") public String delete(@PathVariable Long id) { service.deleteEmployee(id); return "Employee with id " + id + " deleted"; } }
- Client sends
GET /api/employees/5 - Controller receives request → calls
service.getEmployeeById(5) - Service runs business logic → calls
repository.findById(5) - Repository runs SQL →
SELECT * FROM employees WHERE id = 5 - Database returns row → Repository converts to Employee object
- Response travels back up: Repository → Service → Controller → Client (as JSON)
8. application.properties
The application.properties file (in src/main/resources/) configures your Spring Boot app — database connection, server settings, JPA behavior, etc.
Complete Configuration Example
# ─── Server Configuration ─── server.port=8080 # Port the app runs on (default: 8080) # ─── Database Connection ─── spring.datasource.url=jdbc:mysql://localhost:3306/employeedb spring.datasource.username=root # Database username spring.datasource.password=password # Database password spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver # ─── JPA / Hibernate Settings ─── spring.jpa.hibernate.ddl-auto=update # Auto-create/update tables spring.jpa.show-sql=true # Print SQL queries in console spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect
ddl-auto Values — Important for Exams
| Value | What It Does | When to Use |
|---|---|---|
create | Drops all tables, creates fresh every time app starts | Testing only (DESTROYS data) |
update | Updates tables if entity changes, keeps existing data | Development (most common) |
validate | Only validates schema — doesn't change anything | Production |
none | Does nothing — you manage schema manually | Production |
create-drop | Creates on startup, drops on shutdown | Unit testing only |
ddl-auto ask: "Which value preserves existing data while updating schema?" — Answer: update. "Which drops and recreates tables?" — Answer: create.
create deletes ALL your data every time the application restarts. Use update during development and validate or none in production.
9. Dependency Injection (DI)
What is Dependency Injection?
Dependency Injection means objects receive their dependencies from the outside instead of creating them internally. Spring creates and manages all objects (called "beans") and injects them where needed.
Without DI (Bad — Tight Coupling)
public class EmployeeService { // Creating dependency manually — TIGHT COUPLING private EmployeeRepository repo = new EmployeeRepository(); // BAD! }
With DI (Good — Loose Coupling)
@Service public class EmployeeService { // Spring injects the dependency — LOOSE COUPLING @Autowired // Spring creates and injects this private EmployeeRepository repo; // GOOD! }
Constructor Injection (Preferred)
@Service public class EmployeeService { private final EmployeeRepository repo; // Declared as final // Constructor injection — BEST PRACTICE public EmployeeService(EmployeeRepository repo) { this.repo = repo; // Spring passes repo here } // When there's only ONE constructor, @Autowired is optional }
Field Injection vs Constructor Injection
| Aspect | Field Injection (@Autowired on field) | Constructor Injection (Preferred) |
|---|---|---|
| Syntax | @Autowired private Repo repo; | public Service(Repo repo) { this.repo = repo; } |
| Testability | Hard to test (need reflection) | Easy to test (pass mock in constructor) |
| Immutability | Field can change | Field is final — cannot change |
| Best practice? | No (but commonly used in tutorials) | Yes — recommended by Spring team |
- Loose coupling — classes don't depend on specific implementations
- Testability — you can inject mock objects during testing
- Flexibility — swap implementations without changing code
- Spring manages lifecycle — you don't worry about creating/destroying objects
10. Exception Handling
When something goes wrong (e.g., employee not found), you want to return a clean error response instead of a stack trace. Spring Boot provides annotations for this.
Custom Exception Class
public class ResourceNotFoundException extends RuntimeException { public ResourceNotFoundException(String message) { super(message); // Pass message to parent RuntimeException } }
Using @ExceptionHandler (Local — Single Controller)
@RestController @RequestMapping("/api/employees") public class EmployeeController { @GetMapping("/{id}") public Employee getById(@PathVariable Long id) { return service.getEmployeeById(id); // Throws exception if not found } // Handles ResourceNotFoundException in THIS controller only @ExceptionHandler(ResourceNotFoundException.class) public String handleNotFound(ResourceNotFoundException ex) { return ex.getMessage(); // Return error message as response } }
Using @RestControllerAdvice (Global — All Controllers)
@RestControllerAdvice // Applies to ALL controllers in the application public class GlobalExceptionHandler { // Handle specific exception @ExceptionHandler(ResourceNotFoundException.class) public Map<String, String> handleNotFound(ResourceNotFoundException ex) { Map<String, String> error = new HashMap<>(); error.put("error", ex.getMessage()); error.put("status", "404"); return error; // Returns JSON: {"error": "...", "status": "404"} } // Handle all other exceptions @ExceptionHandler(Exception.class) public Map<String, String> handleGeneral(Exception ex) { Map<String, String> error = new HashMap<>(); error.put("error", "Something went wrong"); error.put("status", "500"); return error; } }
@ExceptionHandlerinside a controller — handles exceptions for that controller only@RestControllerAdviceon a separate class — handles exceptions for all controllers (global)@ControllerAdvice= same but returns views.@RestControllerAdvice= returns JSON/data.
11. Spring Boot vs Servlet Comparison
| Feature | Servlets | Spring Boot |
|---|---|---|
| Configuration | web.xml (XML-based) | Annotations + application.properties |
| Server | External Tomcat required | Embedded Tomcat (built-in) |
| HTTP handling | doGet(), doPost() methods | @GetMapping, @PostMapping annotations |
| Request data | request.getParameter() | @PathVariable, @RequestBody |
| Response format | Manual response.getWriter().write() | Automatic JSON conversion |
| Database | Manual JDBC code | JPA Repository (automatic) |
| Code needed | ~100 lines for basic CRUD | ~30 lines for same CRUD |
| Dependency management | Manually add each JAR | Starter dependencies in pom.xml |
| Boilerplate | Lots of repetitive code | Minimal — convention over config |
| Modern projects? | Rarely used directly | Industry standard |
protected void doGet(HttpServletRequest request, HttpServletResponse response) { String id = request.getParameter("id"); // Manual JDBC connection, query, result parsing... response.setContentType("application/json"); response.getWriter().write(jsonString); }Spring Boot (clean):
@GetMapping("/{id}") public Employee getById(@PathVariable Long id) { return service.findById(id); // Auto-converts to JSON }
12. Practice Questions
/employees/5?spring.jpa.hibernate.ddl-auto=update do?update modifies the table structure when entity classes change but keeps existing data. create drops and recreates (data lost). validate only checks. none does nothing.new.server.port=9090. Port 80 is HTTP default, 443 is HTTPS default, 3000 is commonly used by Node.js.final (immutable) and makes testing easier because you can pass mock objects directly through the constructor. Field injection requires reflection for testing.findById() return in JpaRepository?.isPresent() to check if found and .get() to retrieve the value. This avoids NullPointerException.