DEV Community

Brendan Akudo
Brendan Akudo

Posted on

Spring Boot for Beginners: The Java Concepts Hiding Under the Hood

If you're learning Java and keep hearing "just use Spring Boot," it can feel like magic. You annotate a class, run one command, and suddenly you have a working REST API with no server setup, no XML config, no boilerplate. That magic isn't magic at all — it's a handful of core Java concepts, wired together cleverly. Once you see them, Spring Boot stops looking mysterious and starts looking like really good engineering.

This article walks through what Spring Boot actually is, then unpacks the Java fundamentals it leans on: OOP and interfaces, annotations, reflection, generics, and the JVM's packaging model. By the end, you'll understand not just how to use Spring Boot, but why it works.

What Spring Boot Actually Is

Spring Boot is a framework built on top of the older Spring Framework. Spring itself solved a real problem: enterprise Java apps in the early 2000s were drowning in XML configuration files just to wire objects together. Spring introduced Dependency Injection and an Inversion of Control (IoC) container to manage that wiring in code instead.

Spring Boot then solved Spring's own problem: even with DI, configuring a Spring app was still tedious. Boot adds auto-configuration, sensible defaults, and an embedded server, so you can go from zero to a running web app with a single class and a couple of annotations.

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


Enter fullscreen mode Exit fullscreen mode

That's a complete runnable web application. To understand why so little code does so much, we need to look at the Java features underneath it. Let's dive in fellas.

1. Object-Oriented Programming: Classes, Interfaces, Polymorphism

Spring is built around plain Java objects. A typical Spring Boot app is layered like this:

Controller — handles HTTP requests
Service — holds business logic
Repository — talks to the database

Each layer is usually defined as an interface with a concrete implementation:

public interface UserService {
    User findById(Long id);
}

@Service
public class UserServiceImpl implements UserService {
    public User findById(Long id) {
        return new User(id, "Teddy");
    }
}

Enter fullscreen mode Exit fullscreen mode

Why bother with an interface if there's only one implementation? Because Spring depends on Java's polymorphism. Other classes reference UserService, not UserServiceImpl. That means you can swap implementations — a mock for testing, a caching decorator, a completely different data source — without touching any code that depends on the interface. This is the Java principle "program to an interface, not an implementation," and Spring is designed around it from top to bottom.

2. Dependency Injection and Inversion of Control

In plain Java, if a class needs a collaborator, it usually creates it itself:

public class UserController {
    private UserService userService = new UserServiceImpl();
}

Enter fullscreen mode Exit fullscreen mode

This tightly couples UserController to one specific implementation. Testing it means dragging along the real UserServiceImpl and everything it depends on.

Spring flips this around. Instead of a class creating its own dependencies, Spring creates the objects for you and hands them over — this is Dependency Injection (DI). The framework, not your code, decides how objects get wired together — that inversion of responsibility is what "Inversion of Control" means.

@RestController
public class UserController {

    private final UserService userService;

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

Enter fullscreen mode Exit fullscreen mode

You never write new UserServiceImpl() anywhere. At startup, Spring scans your codebase, finds a class annotated @Service that implements UserService, creates an instance of it (a bean), and injects it into UserController's constructor automatically. This single idea — objects declare what they need instead of constructing it — is the backbone of the entire framework, and it's a pattern worth understanding even outside Spring, since it's core to good Java design in general.

3. Annotations: Metadata That Drives Behavior

You've already seen @SpringBootApplication, @Service, and @RestController. Annotations are one of Java's most underrated features: they attach metadata to a class, method, or field without changing its logic.

On their own, annotations do nothing. @Service doesn't run any code by itself — it's just a marker. It's the framework reading that marker that gives it power. Spring scans your classes at startup, checks which ones carry which annotations, and acts accordingly.
Understanding annotations as "metadata read by a framework" rather than "magic keywords" demystifies most of Spring instantly. The same mechanism powers testing frameworks like JUnit, ORMs like Hibernate, and serialization libraries like Jackson.

4. Reflection: How Annotations Actually Get Read

So how does Spring know, at runtime, which classes have @Service on them, or which fields are marked @Autowired? Through Java's Reflection API — the ability for a running program to inspect its own classes, methods, fields, and annotations.

A simplified illustration of what Spring is roughly doing internally:

Class<?> clazz = UserServiceImpl.class;
if (clazz.isAnnotationPresent(Service.class)) {
    Object instance = clazz.getDeclaredConstructor().newInstance();
}
Enter fullscreen mode Exit fullscreen mode

This code inspects a class at runtime, checks its annotations, and creates an instance dynamically — all without knowing the exact class name at compile time. Reflection is slower than direct code, which is why Spring does this scanning once at startup and caches the results, rather than repeating it on every request. It's also why a Spring Boot app has a noticeable startup time compared to a bare Java program — that startup cost is the reflection-based wiring happening.

5. Generics: Type-Safe, Reusable Components

Spring's data layer leans heavily on generics. Consider the repository interface Spring Data JPA gives you almost for free:

public interface UserRepository extends JpaRepository<User, Long> {
}
Enter fullscreen mode Exit fullscreen mode

That's it — no implementation required. JpaRepository is a generic interface: User is the entity type, Long is the type of its ID. Generics let Spring provide one reusable, type-checked implementation of common database operations (save, findById, delete, findAll) that works for any entity, without writing separate code for User, Product, or Order. The compiler enforces that you can't accidentally pass a String where an ID Long is expected — type safety, without runtime casting.

6. POJOs and the JavaBean Convention

Spring frequently works with simple data-holder classes:

public class User {
    private Long id;
    private String name;

    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; }
}
Enter fullscreen mode Exit fullscreen mode

This is a POJO (Plain Old Java Object) following the JavaBean convention: private fields, a no-argument constructor, and public getters/setters. This convention isn't required by the Java language itself — it's a widely followed pattern. Libraries like Jackson (which Spring Boot uses to convert Java objects to JSON and back) rely on reflection to find these getters and setters by name. That's why a field named name needs a method literally named getName() — the library is looking for that exact pattern.

7. The JVM, JARs, and the Embedded Server

Traditionally, a Java web app was packaged as a WAR file and deployed onto an external server like Tomcat. Spring Boot changes this: it packages an embedded Tomcat (or Jetty, or Undertow) inside your application, and bundles everything into a single executable JAR.

./mvnw clean package
java -jar target/demo-0.0.1-SNAPSHOT.jar
Enter fullscreen mode Exit fullscreen mode

That JAR contains your compiled classes, all dependencies, and a runnable server, all executed by the JVM with one command. This is possible because of how the JVM loads classes from JAR files at runtime and because build tools like Maven or Gradle handle dependency resolution — pulling in Spring's libraries (and their dependencies) from a repository automatically, based on the pom.xml or build.gradle file you declare.

Putting It Together: A Minimal REST Endpoint

With all these pieces in place, a working endpoint is short:

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

    private final UserService userService;

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

    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) {
        return userService.findById(id);
    }
}
Enter fullscreen mode Exit fullscreen mode

Walking through what happens when this app starts and a request comes in:

Spring Boot scans the classpath via reflection, finds classes annotated @Service, @Repository, @RestController, and registers them as beans.
It sees UserController needs a UserService in its constructor and injects the matching bean — DI in action.
The embedded server starts, packaged inside the JAR, no external Tomcat needed.
A GET /api/users/5 request arrives. Spring maps it to getUser, using @GetMapping metadata.
The returned User POJO gets serialized to JSON by Jackson, using reflection to read its getters.

Every step traces back to a Java fundamental: interfaces, annotations, reflection, generics, or the JVM's classloading model. Spring Boot doesn't replace these concepts — it orchestrates them.

Wrapping Up

Spring Boot feels like magic mainly because it hides repetitive plumbing behind annotations and sensible defaults. But nothing happening under the hood is exotic — it's core Java, applied consistently and well. Once you can name what's happening at each step — DI, reflection reading annotations, generics giving you type-safe reusable code — the framework becomes predictable instead of mysterious.

If you're coming from a language without this kind of reflection-and-annotation-driven framework culture, it's worth sitting with these concepts individually before layering Spring on top. They pay off well beyond Spring Boot itself — reflection and annotations show up in testing frameworks, ORMs, and serialization libraries across the Java ecosystem.

Have you encountered Spring boot and Java before? If yes what was your experience? I'd love feedback from you guys.
Happy Coding ladies and gentlemen.

Top comments (0)