Spring and Spring Boot: From IoC to Production-Ready REST APIs

From Spring to a real Java backend application
Java backend development runs on Spring more than almost any other framework — and Spring Boot is the reason most developers actually enjoy building with it. This guide walks the full path: what Spring and Spring Boot actually are, the core ideas (IoC, Dependency Injection, Beans) that make Spring’s design genuinely elegant, how a request flows through a real layered application, and how all of it comes together in a small, practical REST API — ending with how a Spring Boot application actually behaves in production.

What Is Spring and Why Is It Used for Java Backend Development
Spring is a comprehensive framework for building Java applications — providing infrastructure for object management, data access, security, web applications, and more, so developers don’t have to build these foundational pieces themselves for every project.
Before Spring, plain Java backend development meant manually wiring together objects, managing their lifecycles, and handling a lot of repetitive infrastructure code by hand. Spring’s core contribution — the IoC container (covered shortly) — takes over that responsibility, letting developers focus on actual business logic instead.
What Is Spring Boot and How Does It Simplify Spring Application Development
Spring Boot is not a different framework from Spring — it’s a layer built on top of Spring, designed to eliminate the tedious manual configuration classic Spring applications required.
Spring vs Spring Boot

A useful analogy: Spring is like a fully equipped kitchen with every tool available — powerful, but you have to set everything up yourself. Spring Boot is that same kitchen, pre-arranged with everything in sensible, ready-to-use positions, so you can start cooking almost immediately.

What Is IoC (Inversion of Control)
Inversion of Control is a design principle where the framework, not your own code, is responsible for creating and managing objects and their relationships.
In plain Java, your code decides when to create an object: new UserService(). With IoC, that responsibility is inverted — you describe what you need, and the Spring container creates and hands it to you.
The manager analogy
Think of a company where employees don’t hire their own coworkers — a manager (the Spring container) is responsible for hiring the right people and assigning them to the right teams. Employees simply show up and work with whoever the manager has already arranged for them — they don’t manage that process themselves.

What Is Dependency Injection and How Does It Help Create Loosely Coupled Applications
Dependency Injection (DI) is the specific mechanism IoC uses — the Spring container injects (provides) an object’s required dependencies, rather than that object creating them itself.
@Service
public class OrderService {
private final PaymentService paymentService;
@Autowired
public OrderService(PaymentService paymentService) {
this.paymentService = paymentService; // injected by Spring, not created manually
}
}
IoC vs Dependency Injection
IoC is the broader principle — “the framework controls object creation.” Dependency Injection is the specific technique Spring uses to achieve it. They’re related, but not identical — DI is one concrete implementation of the IoC idea.
Why this creates loosely coupled applications
OrderService doesn't know or care how PaymentService was built — it simply receives a working instance. This means PaymentService could be swapped for a different implementation entirely, without changing OrderService's code at all — exactly what "loose coupling" means in practice.

What Are Spring Beans and How Does Spring Create and Manage Them
A Bean is simply an object whose creation and lifecycle are managed by the Spring container, instead of being manually instantiated in your own code.
The factory analogy
Think of the Spring container as a factory: you register a blueprint (a class, marked with an appropriate annotation), and the factory produces and manages instances of it — deciding when they’re created, how long they live, and handing them out wherever they’re needed.
@Service
public class NotificationService {
public void send(String message) {
System.out.println("Sending: " + message);
}
}
Because NotificationService is annotated, Spring automatically creates it as a Bean during startup, and injects it anywhere it's needed via @Autowired — you never write new NotificationService() yourself.

Key Spring Annotations: @Component, @Service, @Repository, and More
These annotations all mark a class as a Spring-managed Bean — but with different intended roles, making code more self-documenting.
- @Component — the generic, general-purpose annotation for any Spring-managed class
- @Service — a specialized @Component, intended for classes containing business logic
- @Repository — a specialized @Component, intended for data-access classes, with added exception translation for database operations
- @Controller / @RestController — specialized components handling incoming web requests (covered shortly)
@Component vs @Service vs @Repository
Technically, all three register a Bean the same underlying way — the distinction is primarily about communicating intent clearly to other developers (and unlocking some annotation-specific extra behavior, like @Repository's exception handling).

The restaurant analogy, tying it together
Think of a restaurant: the waiter (@Controller) takes the order from the customer; the chef (@Service) actually prepares the dish, applying the real recipe/logic; the supplier (@Repository) fetches the raw ingredients from storage. Each role is distinct, even though all three are simply "employees" (Beans) working within the same restaurant (application).
Spring Boot Auto-Configuration and Starters
Auto-Configuration
Spring Boot examines what’s on your project’s classpath and automatically configures sensible defaults accordingly — if it detects a database driver, for instance, it can automatically configure a data source, without you writing that configuration by hand.

Important misconception to avoid: Spring Boot reduces configuration — it does not eliminate it. You can, and often should, override defaults explicitly for real production needs.
Starters
Starters are curated dependency bundles — spring-boot-starter-web pulls in everything needed to build a web application (embedded server, MVC support, JSON handling) as one single dependency, instead of manually tracking and aligning versions of a dozen separate libraries yourself.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
Configuration: application.properties / application.yml and Profiles
Spring Boot applications are configured through a central file — either application.properties or application.yml:
server.port=8080
spring.datasource.url=jdbc:mysql://localhost:3306/school
spring.datasource.username=root
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/school
username: root
Profiles
Profiles let you maintain separate configuration sets for different environments (development, testing, production) — switching which one is active without changing your actual code:
# application-dev.properties
spring.datasource.url=jdbc:mysql://localhost:3306/dev_db
# application-prod.properties
spring.datasource.url=jdbc:mysql://prod-server:3306/prod_db
Building REST APIs Using Spring Boot
@Controller vs @RestController
@Controller is intended for traditional web applications returning full HTML views. @RestController (a combination of @Controller and @ResponseBody) is intended for REST APIs — automatically serializing returned objects (like Java objects) directly into JSON, which is exactly what most modern backend APIs need.
@RestController
@RequestMapping("/api/students")
public class StudentController {
@Autowired
private StudentService studentService;
@GetMapping("/{id}")
public Student getStudent(@PathVariable Long id) {
return studentService.getStudentById(id);
}
}
How Controller, Service, and Repository Layers Work Together

Each layer has one clear job: the Controller handles HTTP specifics (request/response, status codes); the Service contains the actual business logic; the Repository handles data access. A common mistake, worth naming directly: Controllers should not normally contain business logic — that responsibility belongs in the Service layer, keeping Controllers thin and focused purely on the web-facing concerns.
Database Integration: Spring Data JPA, Entities, and Repositories
Entity
An Entity is a Java class mapped directly to a database table:
@Entity
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String course;
}
Repository
Spring Data JPA lets you define a repository interface, and Spring automatically generates the implementation — including standard CRUD operations — without you writing the underlying SQL yourself:
public interface StudentRepository extends JpaRepository<Student, Long> {
}This one interface alone provides save(), findById(), findAll(), deleteById(), and more — genuinely working, real CRUD operations, generated automatically.
JPA vs Hibernate
JPA (Java Persistence API) is a specification — a set of rules describing how object-relational mapping should work in Java. Hibernate is the most widely used implementation of that specification. They’re not interchangeable terms: JPA defines the contract; Hibernate (by default, in most Spring Boot projects) is what actually fulfills it underneath.

Validation and Exception Handling
Validation
public class StudentRequest {
@NotBlank(message = "Name is required")
private String name;
@NotBlank(message = "Course is required")
private String course;
}@PostMapping
public Student createStudent(@Valid @RequestBody StudentRequest request) {
return studentService.createStudent(request);
}
@Valid triggers validation automatically before the method body runs — invalid requests are rejected early, with clear error messages, without manually writing validation checks by hand in every controller method.
Exception Handling
@ExceptionHandler(StudentNotFoundException.class)
public ResponseEntity<String> handleNotFound(StudentNotFoundException ex) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.getMessage());
}
Centralized exception handling (typically via @ControllerAdvice) lets you handle errors consistently across the entire application, instead of repeating try/catch logic in every individual controller method — worth noting: client-side validation alone is never sufficient; server-side validation like this remains essential for real security and data integrity.
Spring Security: Authentication and Authorization
Authentication vs Authorization
Authentication verifies who a user is (login credentials). Authorization determines what an authenticated user is allowed to do (permissions, roles) — the same distinction covered in depth in this series’s dedicated security article.
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
);
return http.build();
}
}
Spring Security integrates directly with the layered architecture already covered — protecting specific routes based on roles, working alongside authentication mechanisms like JWT (covered in this series’s dedicated JWT article) or session-based login.
Testing Spring Boot Applications
Unit Testing
Tests an individual component in isolation, typically mocking its dependencies:
@Test
void shouldReturnStudentById() {
when(studentRepository.findById(1L)).thenReturn(Optional.of(sampleStudent));
Student result = studentService.getStudentById(1L);
assertEquals("Aarav", result.getName());
}
Integration Testing
Tests multiple layers working together — often including the actual database (or an in-memory test database):
@SpringBootTest
@AutoConfigureMockMvc
class StudentControllerIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Test
void shouldReturnStudentList() throws Exception {
mockMvc.perform(get("/api/students"))
.andExpect(status().isOk());
}
}

Practical Example: Student Course Management REST API
Bringing every concept together into one small, coherent example:

@Entity
public class Student {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String course;
}
public interface StudentRepository extends JpaRepository<Student, Long> {}
@Service
public class StudentService {
@Autowired
private StudentRepository studentRepository;
public Student createStudent(Student student) {
return studentRepository.save(student);
}
public Student getStudentById(Long id) {
return studentRepository.findById(id)
.orElseThrow(() -> new StudentNotFoundException("Student not found"));
}
}
@RestController
@RequestMapping("/api/students")
public class StudentController {
@Autowired
private StudentService studentService;
@PostMapping
public ResponseEntity<Student> create(@Valid @RequestBody Student student) {
return ResponseEntity.status(HttpStatus.CREATED).body(studentService.createStudent(student));
}
@GetMapping("/{id}")
public Student getById(@PathVariable Long id) {
return studentService.getStudentById(id);
}
}
This small example genuinely demonstrates every core concept: Dependency Injection (@Autowired), Beans (@Service, @Repository, @RestController), REST endpoints, database integration (via JpaRepository), validation (@Valid), and a foundation ready for exception handling and testing exactly as shown above.
Connecting Spring Boot to Backend and Production Systems
Microservices and service-to-service communication
Larger systems often split a single application into multiple independent Spring Boot services — each with its own responsibility, communicating over REST APIs or messaging systems (like Kafka, covered elsewhere in this series) rather than sharing one codebase directly.
Deployment
Spring Boot applications are commonly packaged as a self-contained JAR file (thanks to the embedded server), and increasingly deployed via Docker containers onto cloud platforms — a natural fit for modern deployment pipelines.
Production concerns

Real production Spring Boot systems care about: scalability (running multiple instances behind a load balancer, as covered in this series’s scalable systems article), caching, logging and monitoring (health checks, metrics), configuration management across environments (via profiles), and security hardening beyond the basics shown here.
Common Mistakes and Misconceptions
- Spring and Spring Boot are not different frameworks — Spring Boot is built directly on top of Spring
- IoC and Dependency Injection are not identical — IoC is the principle; DI is Spring’s specific mechanism for it
- Not every Java object is a Spring Bean — only classes explicitly registered (via annotations or configuration) become Beans
- @Service is a specialized @Component, not a separate, unrelated annotation
- @Autowired is not the definition of Dependency Injection — it's one specific way of requesting an already-injected dependency
- Spring Boot reduces configuration, it does not eliminate it — real applications still need explicit configuration for real needs
- Controllers should not normally contain business logic — that belongs in the Service layer
- JPA and Hibernate are not interchangeable — JPA is the specification; Hibernate is an implementation of it
- Client-side validation alone is not enough — server-side validation and security remain essential
Final Takeaway
Spring’s real contribution is inversion of control — letting a container manage object creation and wiring, so your code can simply declare what it needs and receive it, cleanly, via Dependency Injection. Beans are the objects that container manages; annotations like @Service and @Repository communicate each Bean's intended role. Spring Boot removes the tedious manual configuration classic Spring required, through auto-configuration and starters, letting you go from an empty project to a working REST API remarkably quickly. Layer that API cleanly — Controller, Service, Repository — connect it to a real database through Spring Data JPA, add validation, exception handling, and security, and you have exactly the shape of a real, production-ready Java backend application: the same shape this guide's Student Course Management example demonstrates end to end. From here, Spring Security, testing, Docker, and microservices are natural next steps, each building directly on the foundation covered in this guide.
Frequently Asked Questions
Do I need to learn plain Spring before Spring Boot?
> Not strictly — most developers today learn Spring Boot directly, since it’s how the vast majority of new Spring applications are built. Understanding core Spring concepts (IoC, DI, Beans) remains essential either way, since Spring Boot builds directly on top of them rather than replacing them.
Is Hibernate required to use Spring Data JPA?
> Not technically — JPA is a specification with multiple possible implementations, but Hibernate is the default and by far the most common one used in Spring Boot applications, to the point that they’re often used together without much separate configuration.
What’s the difference between @Autowired field injection and constructor injection?
> Both achieve Dependency Injection, but constructor injection (shown in this guide’s examples) is generally preferred — it makes dependencies explicit, supports immutability (final fields), and makes testing easier, compared to field injection.
Is Spring Boot only suitable for large enterprise applications?
> No — its auto-configuration and starters make it genuinely convenient for small projects and learning too, not just large-scale systems. Its production-oriented features simply become more valuable as an application grows.
Originally published by Mr Madhukar
Read the complete article on Medium with full formatting & reader responses.