Home Java Spring Boot Introduction: Why It Exists and How to Build Your First App

Spring Boot Introduction: Why It Exists and How to Build Your First App

In Plain English 🔥
Imagine you want to bake a cake. Traditional Spring is like being handed a bag of flour, eggs, sugar, and a broken oven — you assemble everything yourself. Spring Boot is like getting a pre-heated oven, a mixing bowl already set up, and a recipe card that says 'just add your own flavour.' It makes all the boring setup decisions for you so you can focus on what your app actually does. That's it. Convention over configuration.
⚡ Quick Answer
Imagine you want to bake a cake. Traditional Spring is like being handed a bag of flour, eggs, sugar, and a broken oven — you assemble everything yourself. Spring Boot is like getting a pre-heated oven, a mixing bowl already set up, and a recipe card that says 'just add your own flavour.' It makes all the boring setup decisions for you so you can focus on what your app actually does. That's it. Convention over configuration.

Every Java developer hits the same wall. You have a great idea for a web service, but before writing a single line of business logic you're buried in XML files, manually wiring beans, configuring a servlet container, and fighting dependency version mismatches. By the time your environment works, you've forgotten what you were building. Spring Boot was created specifically to eliminate that wall — and it's why almost every new Java backend project in the industry starts with it today.

The core problem Spring Boot solves is bootstrapping friction. The original Spring Framework is powerful but famously verbose. A simple REST endpoint could require a web.xml, a Spring MVC dispatcher config, a context configuration class, and a dozen explicit bean definitions. Spring Boot replaces all of that with intelligent defaults and auto-configuration — it looks at what's on your classpath and wires things up for you automatically. You bring the feature; Spring Boot brings the scaffolding.

By the end of this article you'll understand exactly what Spring Boot is (and isn't), why auto-configuration is the secret engine behind it, how to build and run a real REST API in minutes, and where the common traps are that catch even experienced developers off guard. You'll be ready to explain it confidently in an interview and use it correctly in production.

What Spring Boot Actually Is — and What It Isn't

Spring Boot is not a replacement for the Spring Framework. This trips up a lot of developers. Spring Boot is a launcher and opinion layer built on top of Spring. It uses the same Spring core — dependency injection, Spring MVC, Spring Data — but makes opinionated decisions about how those pieces fit together so you don't have to.

Three pillars hold up everything Spring Boot does:

  1. Auto-Configuration — Spring Boot scans your classpath and automatically creates beans you'd otherwise define manually. If it sees spring-boot-starter-web on the classpath, it auto-configures an embedded Tomcat server, a DispatcherServlet, and Jackson for JSON serialization. No XML. No boilerplate.
  1. Starter Dependencies — Instead of hunting for compatible Maven/Gradle versions of 12 different Spring libraries, you add one starter like spring-boot-starter-data-jpa and it pulls in everything that works together, versioned correctly.
  1. Embedded Server — Your application ships as a single executable JAR with an embedded Tomcat (or Jetty/Undertow). No deploying WAR files to an external server. You run java -jar myapp.jar and you're live.

The key mental model: Spring Boot doesn't add features to Spring — it removes the work of configuring them.

BookstoreApplication.java · JAVA
1234567891011121314151617181920212223242526
package io.thecodeforge.bookstore;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @SpringBootApplication is a convenience annotation that combines three annotations:
 *   1. @Configuration       — marks this class as a source of bean definitions
 *   2. @EnableAutoConfiguration — tells Spring Boot to start auto-configuring beans
 *                               based on what's on the classpath
 *   3. @ComponentScan       — tells Spring to scan this package (and sub-packages)
 *                               for @Component, @Service, @Repository, @Controller etc.
 *
 * You almost never split these three apart in a real project.
 */
@SpringBootApplication
public class BookstoreApplication {

    public static void main(String[] args) {
        // SpringApplication.run() does the heavy lifting:
        // - Creates the ApplicationContext
        // - Registers all auto-configured beans
        // - Starts the embedded Tomcat server on port 8080
        SpringApplication.run(BookstoreApplication.class, args);
    }
}
▶ Output
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.2.0)

2024-01-15T10:23:41.123Z INFO --- [main] i.t.bookstore.BookstoreApplication : Starting BookstoreApplication
2024-01-15T10:23:42.456Z INFO --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8080
2024-01-15T10:23:42.461Z INFO --- [main] i.t.bookstore.BookstoreApplication : Started BookstoreApplication in 1.847 seconds
🔥
Key Insight:@SpringBootApplication is shorthand for three annotations in one. If you ever need to customize component scanning (e.g., scan a different base package), you can break them apart: use @Configuration + @EnableAutoConfiguration + @ComponentScan(basePackages = "com.other.package") separately.

Auto-Configuration: The Engine Under the Hood

Auto-configuration is the most powerful — and most misunderstood — feature in Spring Boot. When your app starts, Spring Boot runs through a list of candidate configuration classes defined in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. Each one is guarded by @ConditionalOn* annotations that only activate the configuration if certain conditions are true.

For example, DataSourceAutoConfiguration only fires if there's a JDBC driver on the classpath AND no DataSource bean has been manually defined. This conditional logic is the key: auto-configuration never overwrites your explicit choices. It fills in the gaps.

This is why Spring Boot is described as 'opinionated but overridable.' You can override any auto-configured bean by simply declaring your own. Want a custom Jackson ObjectMapper? Define one as a @Bean and Spring Boot's JacksonAutoConfiguration backs off automatically.

Understanding this conditional model is what separates developers who use Spring Boot from developers who truly understand it. When something isn't working the way you expect, the first question to ask is: 'Is auto-configuration being triggered or not?' Run your app with --debug flag to get a full auto-configuration report printed to the console.

BookController.java · JAVA
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061
package io.thecodeforge.bookstore.controller;

import io.thecodeforge.bookstore.model.Book;
import io.thecodeforge.bookstore.service.BookService;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 * @RestController = @Controller + @ResponseBody
 * Every method return value is automatically serialized to JSON by
 * Jackson — which was auto-configured because spring-boot-starter-web
 * is on the classpath. You didn't configure Jackson. Spring Boot did.
 */
@RestController
@RequestMapping("/api/books")  // All endpoints in this class are prefixed with /api/books
public class BookController {

    // Spring injects BookService automatically via constructor injection.
    // This is the preferred injection style — it makes the dependency explicit
    // and the class easy to unit test without Spring.
    private final BookService bookService;

    public BookController(BookService bookService) {
        this.bookService = bookService;
    }

    /**
     * GET /api/books — returns all books as a JSON array.
     * ResponseEntity gives us full control over the HTTP status code.
     */
    @GetMapping
    public ResponseEntity<List<Book>> getAllBooks() {
        List<Book> books = bookService.findAllBooks();
        return ResponseEntity.ok(books);  // 200 OK + JSON body
    }

    /**
     * POST /api/books — creates a new book.
     * @RequestBody tells Spring to deserialize the incoming JSON into a Book object.
     * @ResponseStatus sets the default HTTP response code to 201 Created.
     */
    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)  // Returns 201 instead of default 200
    public Book createBook(@RequestBody Book newBook) {
        return bookService.saveBook(newBook);  // Returns saved book with generated ID
    }

    /**
     * GET /api/books/{id} — find a specific book by its ID.
     * Returns 404 if not found — handled cleanly via Optional.
     */
    @GetMapping("/{bookId}")
    public ResponseEntity<Book> getBookById(@PathVariable Long bookId) {
        return bookService.findBookById(bookId)
                .map(ResponseEntity::ok)              // Found: 200 OK with book
                .orElse(ResponseEntity.notFound().build()); // Not found: 404
    }
}
▶ Output
# After POST /api/books with body {"title":"Clean Code","author":"Robert Martin","price":29.99}
HTTP 201 Created
{
"id": 1,
"title": "Clean Code",
"author": "Robert Martin",
"price": 29.99
}

# After GET /api/books
HTTP 200 OK
[
{
"id": 1,
"title": "Clean Code",
"author": "Robert Martin",
"price": 29.99
}
]

# After GET /api/books/999
HTTP 404 Not Found
⚠️
Debug Auto-Config Like a Pro:Run your Spring Boot app with java -jar myapp.jar --debug and look for the 'CONDITIONS EVALUATION REPORT' section. It lists every auto-configuration class and tells you exactly why it was applied or skipped. This is your best friend when something unexpectedly isn't being configured.

Building the Complete Bookstore: Service, Model and application.properties

A controller without a service and model isn't a real app — it's a demo. Let's complete the picture. The service layer is where your business logic lives. The model defines your data shape. And application.properties (or application.yml) is where Spring Boot externalizes configuration — port numbers, database URLs, custom properties — all without touching code.

This separation of concerns is not just good practice; it's what makes Spring Boot apps testable and maintainable at scale. You can swap out the BookService implementation for a mock in tests, change the database from H2 to PostgreSQL by editing a single properties file, and change the server port for different environments without recompiling.

Notice how the Book model below uses @Entity and @Id — this wires into Spring Data JPA, which is itself auto-configured when you add spring-boot-starter-data-jpa. The pattern repeats: add a starter, write your domain code, Spring Boot wires the infrastructure.

Book.java · JAVA
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
// ─────────────────────────────────────────────
// FILE 1: Book.java  (the Model / Entity)
// ─────────────────────────────────────────────
package io.thecodeforge.bookstore.model;

import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Positive;

/**
 * @Entity marks this class as a JPA entity — Spring Data JPA will
 * automatically create a 'book' table in the database for this.
 * With H2 in-memory DB on the classpath, no SQL setup needed.
 */
@Entity
@Table(name = "books")
public class Book {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY) // Auto-increment primary key
    private Long id;

    @NotBlank(message = "Title cannot be empty")  // Validated by @Valid in controller
    @Column(nullable = false)
    private String title;

    @NotBlank(message = "Author cannot be empty")
    private String author;

    @Positive(message = "Price must be greater than zero")
    private Double price;

    // JPA requires a no-arg constructor
    protected Book() {}

    public Book(String title, String author, Double price) {
        this.title = title;
        this.author = author;
        this.price = price;
    }

    // Getters and setters — needed for Jackson JSON serialization
    public Long getId() { return id; }
    public String getTitle() { return title; }
    public void setTitle(String title) { this.title = title; }
    public String getAuthor() { return author; }
    public void setAuthor(String author) { this.author = author; }
    public Double getPrice() { return price; }
    public void setPrice(Double price) { this.price = price; }
}

// ─────────────────────────────────────────────
// FILE 2: BookRepository.java  (the Data Layer)
// ─────────────────────────────────────────────
package io.thecodeforge.bookstore.repository;

import io.thecodeforge.bookstore.model.Book;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

/**
 * JpaRepository<Book, Long> gives us findById, findAll, save, delete etc.
 * for FREESpring Data generates the implementation at runtime.
 * We don't write a single SQL query for standard CRUD operations.
 */
@Repository
public interface BookRepository extends JpaRepository<Book, Long> {
    // Spring Data can generate queries from method names alone:
    // findByAuthor(String author) → SELECT * FROM books WHERE author = ?
    // No SQL needed — the method name IS the query.
}

// ─────────────────────────────────────────────
// FILE 3: BookService.java  (the Business Layer)
// ─────────────────────────────────────────────
package io.thecodeforge.bookstore.service;

import io.thecodeforge.bookstore.model.Book;
import io.thecodeforge.bookstore.repository.BookRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Optional;

/**
 * @Service is a specialization of @Component — it marks this class
 * as a service-layer bean and makes it eligible for component scanning.
 * Functionally identical to @Component, but communicates intent clearly.
 */
@Service
public class BookService {

    private final BookRepository bookRepository;

    // Constructor injection — Spring automatically injects the BookRepository bean.
    // Because there's only ONE constructor, @Autowired is not required in Spring 4.3+.
    public BookService(BookRepository bookRepository) {
        this.bookRepository = bookRepository;
    }

    public List<Book> findAllBooks() {
        return bookRepository.findAll();
    }

    public Optional<Book> findBookById(Long bookId) {
        return bookRepository.findById(bookId);
    }

    @Transactional  // Wraps the DB operation in a transaction — rolls back on exception
    public Book saveBook(Book newBook) {
        return bookRepository.save(newBook);
    }
}

// ─────────────────────────────────────────────
// FILE 4: application.properties
// Location: src/main/resources/application.properties
// ─────────────────────────────────────────────
// server.port=8080                     ← default, shown for clarity
// spring.application.name=bookstore-api
//
// # H2 in-memory database (auto-configured when h2 is on classpath)
// spring.datasource.url=jdbc:h2:mem:bookstoredb
// spring.datasource.driver-class-name=org.h2.Driver
// spring.h2.console.enabled=true       ← access DB GUI at /h2-console
//
// # Show SQL queries in console during development
// spring.jpa.show-sql=true
// spring.jpa.hibernate.ddl-auto=create-drop  ← creates schema on start, drops on stop
▶ Output
# Application starts successfully:
Tomcat started on port(s): 8080
Hibernate: create table books (id bigint generated by default as identity, author varchar(255), price float(53), title varchar(255) not null, primary key (id))
Started BookstoreApplication in 2.341 seconds

# POST /api/books
Hibernate: insert into books (author, price, title) values (?, ?, ?)
HTTP 201 → {"id":1,"title":"Clean Code","author":"Robert Martin","price":29.99}

# GET /api/books
Hibernate: select b1_0.id,b1_0.author,b1_0.price,b1_0.title from books b1_0
HTTP 200 → [{"id":1,"title":"Clean Code","author":"Robert Martin","price":29.99}]
⚠️
Watch Out:spring.jpa.hibernate.ddl-auto=create-drop deletes your entire database schema every time the app stops. It's fine for development but catastrophic in production. For production, use validate (check schema matches entities but don't change it) or none and manage schema with a migration tool like Flyway or Liquibase.
Feature / AspectTraditional Spring MVCSpring Boot
Setup time for a REST endpoint30-60 min (XML + config classes)Under 5 min (starter + annotation)
Server deploymentDeploy WAR to external Tomcat/JBossRun JAR directly — server is embedded
Dependency managementManually match compatible versionsStarter POMs manage versions automatically
Configuration styleMostly explicit XML or Java @ConfigConvention-based with properties override
Auto-configurationNone — everything manually declaredConditional auto-config based on classpath
Production readinessManual setup for metrics/healthActuator adds /health, /metrics instantly
Learning curveSteep — many moving partsGentle start, deep customization available
Best forLegacy enterprise apps already on SpringAny new Java backend project from scratch

🎯 Key Takeaways

  • Spring Boot is a launcher layer on top of Spring — it doesn't replace Spring, it removes the setup work. All the same Spring concepts still apply underneath.
  • Auto-configuration is conditional — it only activates when its @ConditionalOn* conditions are met and always backs off when you define your own bean. Run with --debug to see exactly what fired and why.
  • Constructor injection is the correct way to wire dependencies in Spring Boot — it makes your classes testable without a Spring context and makes dependencies visible and honest.
  • The starter dependency model solves the version-mismatch problem by giving you a single, curated, tested set of compatible library versions — resist the urge to override individual versions unless you have a specific reason.

⚠ Common Mistakes to Avoid

  • Mistake 1: Placing the main class in a non-parent package — If your @SpringBootApplication class is in io.thecodeforge but your controllers are in com.mycompany.controllers, component scanning won't find them and you'll get 404s on every endpoint with no error. Fix: always put the main class at the root package that is the parent of all your other packages, e.g., io.thecodeforge.bookstore.
  • Mistake 2: Using @Autowired on fields instead of constructors — Field injection like @Autowired private BookService bookService works but hides dependencies, makes unit testing painful (you can't inject mocks without a Spring context), and hides circular dependency bugs until runtime. Fix: always use constructor injection. If a class has too many constructor parameters, that's a code smell telling you to split the class.
  • Mistake 3: Forgetting that spring.jpa.hibernate.ddl-auto defaults to create-drop for embedded databases — Developers test locally with H2, everything works, they switch to PostgreSQL in production and suddenly the schema isn't being created and the app crashes immediately. Fix: explicitly set ddl-auto for every environment and use Flyway or Liquibase for schema migrations in staging and production environments.

Interview Questions on This Topic

  • QWhat is the difference between @SpringBootApplication and @EnableAutoConfiguration — and when would you ever split them apart?
  • QHow does Spring Boot auto-configuration know NOT to override a bean you've defined yourself — what mechanism prevents the conflict?
  • QIf you add both spring-boot-starter-web and spring-boot-starter-webflux to the same project, what happens and why?

Frequently Asked Questions

Do I need to know the Spring Framework before learning Spring Boot?

Ideally yes, but you can be productive with Spring Boot without deep Spring knowledge. However, when things go wrong — and they will — understanding Spring's core concepts like the ApplicationContext, bean lifecycle, and dependency injection will save you hours of debugging. Think of Spring as the engine and Spring Boot as the dashboard.

What is the difference between Spring Boot and Spring MVC?

Spring MVC is a web framework inside the Spring ecosystem that handles HTTP requests, routing, and response rendering. Spring Boot is a bootstrapping tool that can use Spring MVC (via spring-boot-starter-web) as one of many possible features. Spring Boot sets up Spring MVC for you automatically — you use both at the same time, but they solve different problems.

Why does Spring Boot embed Tomcat? Isn't it better to use an external server?

Embedding the server is intentional and powerful. It makes your app a self-contained deployable unit — one JAR that runs anywhere Java runs. This is essential for containerized deployments (Docker, Kubernetes) where the environment is ephemeral and you want full control over the server version. External server deployment is still supported via WAR packaging if your organisation requires it.

🔥
TheCodeForge Editorial Team Verified Author

Written and reviewed by senior developers with real-world experience across enterprise, startup and open-source projects. Every article on TheCodeForge is written to be clear, accurate and genuinely useful — not just SEO filler.

← PreviousJava Memory Leaks and PreventionNext →Maven vs Gradle in Java
Forged with 🔥 at TheCodeForge.io — Where Developers Are Forged