Creating a Simple CRUD API with Spring Boot and In-Memory Database

Below is a step-by-step guide to create a simple CRUD API using Spring Boot and an in-memory database like H2. We will use Maven to manage the project’s dependencies.

1. Set Up Your Maven Project

Create a new Maven project or directory for the Spring Boot application.

pom.xml

Here is the file with the necessary dependencies: pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>crud-api</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <name>Spring Boot CRUD API</name>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.5.3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <java.version>21</java.version>
    </properties>

    <dependencies>
        <!-- Spring Boot Starter Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- Spring Boot Starter JPA -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <!-- H2 Database (In-memory database) -->
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>

        <!-- Lombok for simplicity -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <!-- Maven Spring Boot plugin -->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

2. Create the Application Entry Point

src/main/java/org/kodejava/crudapi/CrudApiApplication.java

Create the main class for your Spring Boot application.

package org.kodejava.crudapi;

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

@SpringBootApplication
public class CrudApiApplication {

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

3. Define the Entity

Create a User entity to model your data.

src/main/java/org/kodejava/crudapi/model/User.java

package org.kodejava.crudapi.model;

import jakarta.persistence.*;

import lombok.Data;
import lombok.NoArgsConstructor;

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

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

    private String name;

    private String email;
}

4. Create the Repository

The repository will handle database operations.

src/main/java/org/kodejava/crudapi/repository/UserRepository.java

package org.kodejava.crudapi.repository;

import com.example.crudapi.model.User;
import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User, Long> {
}

5. Define the Service

The service layer will handle business logic.

src/main/java/org/kodejava/crudapi/service/UserService.java

package org.kodejava.crudapi.service;

import com.example.crudapi.model.User;
import com.example.crudapi.repository.UserRepository;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class UserService {

    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

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

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

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

    public User updateUser(Long id, User user) {
        User existingUser = getUserById(id);
        existingUser.setName(user.getName());
        existingUser.setEmail(user.getEmail());
        return userRepository.save(existingUser);
    }

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

6. Create the Controller

The controller defines API endpoints for CRUD operations.

src/main/java/org/kodejava/crudapi/controller/UserController.java

package org.kodejava.crudapi.controller;

import com.example.crudapi.model.User;
import com.example.crudapi.service.UserService;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

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

    private final UserService userService;

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

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

    @GetMapping("/{id}")
    public ResponseEntity<User> getUserById(@PathVariable Long id) {
        return ResponseEntity.ok(userService.getUserById(id));
    }

    @PostMapping
    public ResponseEntity<User> createUser(@RequestBody User user) {
        return ResponseEntity.status(HttpStatus.CREATED).body(userService.createUser(user));
    }

    @PutMapping("/{id}")
    public ResponseEntity<User> updateUser(@PathVariable Long id, @RequestBody User user) {
        return ResponseEntity.ok(userService.updateUser(id, user));
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
        userService.deleteUser(id);
        return ResponseEntity.noContent().build();
    }
}

7. Configure Application Properties

Configure the H2 database in . application.properties

src/main/resources/application.properties

# H2 In-Memory Database Settings
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console

8. Test the Application

Run your Spring Boot application and test the REST APIs using tools like Postman or curl.

  • To get all users:
  GET http://localhost:8080/api/users
  • To get a user by ID:
  GET http://localhost:8080/api/users/{id}
  • To create a user:
  POST http://localhost:8080/api/users
  Content-Type: application/json
  {
      "name": "John Doe",
      "email": "[email protected]"
  }
  • To update a user:
  PUT http://localhost:8080/api/users/{id}
  Content-Type: application/json
  {
      "name": "Jane Doe",
      "email": "[email protected]"
  }
  • To delete a user:
  DELETE http://localhost:8080/api/users/{id}

9. Access the H2 Console

You can access the H2 database console at http://localhost:8080/h2-console.

  • JDBC URL: jdbc:h2:mem:testdb
  • Username: sa
  • Password: password

That’s it! You now have a fully functional CRUD API with Spring Boot and an in-memory H2 database.

Spring Boot Configuration Essentials: application.properties vs application.yml

In Spring Boot, both application.properties and application.yml are configuration files used to define application settings, but they differ mainly in syntax and structure. Here’s a detailed comparison to help you understand their essentials:

1. File Format

application.properties:

  • A key-value pair format, where each property is defined on a new line.
  • A simple and widely used format for configuration files.

Example:

server.port=8080
spring.datasource.url=jdbc:mysql://localhost:3306/mydb
spring.datasource.username=admin
spring.datasource.password=secret

application.yml:

  • A hierarchical data format popular for its readability, using indentation to denote levels.
  • Based on YAML syntax, it is more concise for complex hierarchical configurations.

Example:

server:
  port: 8080

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mydb
    username: admin
    password: secret

2. Structure and Readability

  • application.properties: Flat structure; can get lengthy and harder to read with nested configurations.
  • application.yml: More concise and easier to maintain hierarchical data (e.g., grouping related properties).

For instance:

  • Nested configuration in application.properties:
  spring.datasource.url=jdbc:mysql://localhost:3306/mydb
  spring.datasource.username=admin
  spring.datasource.password=secret
  spring.jpa.hibernate.ddl-auto=update
  • Nested configuration in application.yml:
  spring:
    datasource:
      url: jdbc:mysql://localhost:3306/mydb
      username: admin
      password: secret
    jpa:
      hibernate:
        ddl-auto: update

3. Multi-Profile Support

  • Both formats support profiles (e.g., application-dev.properties or application-dev.yml).
  • However, in application.yml, profiles are defined more elegantly using spring.profiles.

Example in application.properties:

# application-dev.properties
spring.datasource.url=jdbc:mysql://localhost:3306/devdb

Example in application.yml:

spring:
  profiles:
    active: dev

---
spring:
  profiles: dev
  datasource:
    url: jdbc:mysql://localhost:3306/devdb

The --- in YAML separates different profiles, while spring.profiles specifies the active one.

4. Comments

  • application.properties: Use # for single-line comments.
    Example:
  # This sets the server's port
  server.port=8080
  • application.yml: Also uses # for comments.
    Example:
  # This sets the server's port
  server:
    port: 8080

5. Tools and Validation

  • YAML syntax errors (like incorrect indentation) are harder to debug compared to properties.
  • Most IDEs (like IntelliJ IDEA) provide excellent support for both formats, with syntax highlighting and validation tools.

6. When to Use Which?

  • Use application.properties:
    • If you prefer simplicity and are comfortable with key-value pairs.
    • For flat and straightforward configurations.
  • Use application.yml:
    • If you want a more structured and readable format.
    • For more complex hierarchical configurations or when working with deeply nested values.

7. Mixing Both

  • Spring Boot supports both application.properties and application.yml in the same project, but it’s recommended to stick to one for consistency.
  • If both exist, application.properties takes precedence over application.yml (as per Spring Boot’s default property source order).

Summary Table:

Aspect application.properties application.yml
Format Key-value pair Hierarchical YAML format
Readability Harder for nested values Easy to read and maintain with indentation
Profiles Separate files for each profile Inline profiles with --- separator
Error handling Simple; less prone to errors Easy to misconfigure due to indentation
Preference Flat structure Complex or hierarchical configuration

Using Spring Boot DevTools for Faster Development and Live Reload

Spring Boot DevTools is a valuable tool for speeding up the development process by enabling features like automatic application restart and live reload of web content. It is specifically designed to improve the development experience by reducing the time required to restart the application during testing and debugging.

Here is a quick guide to using Spring Boot DevTools for faster development and live reload:


1. Add DevTools Dependency

To use Spring Boot DevTools, include it in your pom.xml (Maven) or build.gradle (Gradle) file.

Using Maven:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <optional>true</optional>
</dependency>

Using Gradle:

implementation 'org.springframework.boot:spring-boot-devtools'

2. Automatic Restart

Spring Boot DevTools triggers an automatic application restart whenever files in the classpath are modified. It uses two classloaders—one for the static resources and one for the application classes—enabling a fast reload experience.

  • Restart Triggering: Files located under /src/main/resources/, /src/main/java/, or any other classpath resources automatically restart the application upon modification.
  • Excluding Certain Files from Restart: You can exclude specific file patterns using the property:
spring.devtools.restart.exclude=static/**,public/**

3. Live Reload (Optional with Browser)

Spring Boot DevTools integrates with LiveReload, so front-end changes (e.g., HTML, CSS, or JavaScript) trigger an automatic browser reload.

Steps for LiveReload:

  1. Install a LiveReload extension in your browser (available for Chrome, Firefox, etc.).
  2. DevTools will automatically enable LiveReload if the extension is active.

If you want to disable the LiveReload capability, use the following property:

spring.devtools.livereload.enabled=false

4. Property Defaults in Development vs. Production

DevTools provides sensible defaults for development environments that are different from production. For instance:

  • Caching is disabled for templates (e.g., Thymeleaf, FreeMarker, etc.).
  • Hibernate auto-detection for changes in the database schema is enabled.

If you want to customize DevTools properties, you can use a dedicated application-dev.properties profile.

5. How to Enable Conditional DevTools Behavior

To avoid shipping DevTools to production, mark it as optional=true in Maven or use a developmentOnly configuration in Gradle.

Example for Gradle:

developmentOnly 'org.springframework.boot:spring-boot-devtools'

6. Disable Restart in Specific Scenarios

If you don’t want restart functionality during your development, you can disable it with the property:

spring.devtools.restart.enabled=false

7. Trigger a Manual Restart

If you want to trigger a restart manually during development, you can:

  • Add or remove files in the /META-INF/spring-devtools.properties directory to trigger a restart.

8. Example Use Case: Thymeleaf or Front-End Modification

If you’re using Thymeleaf templates in a Spring Boot application for the web frontend:

  • Modify an HTML file under /src/main/resources/templates.
  • The browser will refresh automatically (if LiveReload is enabled). You’ll instantly see your changes without manually restarting or refreshing.

Important Notes:

  • Spring Boot DevTools is solely for development purposes and should not be packaged into your production build.
  • If you’re running in Docker or a cloud environment, ensure that file watchers are properly configured for file changes.

Incorporating Spring Boot DevTools provides a more efficient development experience by automating repetitive actions and making live coding efforts more seamless!

Building Your First REST API with Spring Boot in Under 10 Minutes

Creating a simple REST API using Spring Boot can be a smooth process, and it is entirely possible to get it done in under 10 minutes. Here’s a step-by-step guide to building your first REST API:

1. Setup Your Spring Boot Project

  1. Go to Spring Initializr.
  2. Configure the project:
    • Project: Maven.
    • Language: Java.
    • Spring Boot Version: Choose the latest stable version.
    • Dependencies: Add Spring Web (this adds support for creating a REST API).
  3. Click to download the project as a .zip file. Generate

  4. Extract the file, then open it in your favorite IDE like IntelliJ IDEA.

2. Create a REST Controller

A REST controller handles HTTP requests and maps them to methods. You will use the @RestController annotation to create a REST API in Spring Boot.
Create a file under src/main/java/com/example/demo/ named : HelloWorldController.java

package com.example.demo;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api")
public class HelloWorldController {

    @GetMapping("/hello")
    public String helloWorld() {
        return "Hello, World!";
    }
}
  • Annotations Explained:
    • @RestController: Combines @Controller and @ResponseBody, enabling REST-specific behavior.
    • @RequestMapping: Specifies the base path for this controller (e.g., /api).
    • @GetMapping: Handles HTTP GET requests for the /hello sub-endpoint.

3. Run Your Application

  1. Run your application by navigating to the DemoApplication class and executing the method (Run button in IntelliJ). main
  2. By default, your application will start on port 8080.

4. Test Your REST API

Hello, World!

5. (Optional) Add Another Endpoint Returning JSON

If you want to return a JSON response, create a new endpoint:

@GetMapping("/greet")
public Greeting greetUser() {
    return new Greeting("Welcome to Spring Boot!");
}

Create a new class Greeting.java under the same package:

package com.example.demo;

public class Greeting {

    private String message;

    public Greeting(String message) {
        this.message = message;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}
{
    "message": "Welcome to Spring Boot!"
}

6. Complete!

Congratulations, you’ve successfully created your first REST API with Spring Boot!

How Dependency Injection Works in Spring Boot Using @Autowired

Dependency Injection (DI) is a fundamental concept in Spring Boot that facilitates the management of dependencies between various components in your application. Spring Boot primarily uses the @Autowired annotation to enable automatic wiring of beans. Below, I’ll explain how Dependency Injection works using @Autowired in Spring Boot, step by step.

What is Dependency Injection?

Instead of creating dependencies manually within components, Spring’s IoC (Inversion of Control) container injects dependencies into an object automatically. This reduces tight coupling and makes code more reusable and flexible.

How Does @Autowired Work?

@Autowired is used to mark a dependency that Spring should handle automatically. It tells Spring, “Find the appropriate bean in the IoC container and inject it here.”

Here’s how it works:

  1. Mark the class as a Spring Bean using @Component, @Service, @Repository, or by defining it explicitly via @Bean in a configuration class.
  2. Use the @Autowired Annotation to inject a required dependency into a field, constructor, or setter method.

Approaches to Using @Autowired

1. Field Injection

This is the simplest form of dependency injection where the field is annotated with @Autowired.

import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Autowired;

@Component
public class CarService {

    @Autowired
    private Engine engine;

    public void startCar() {
        engine.start();
    }
}
@Component
public class Engine {
    public void start() {
        System.out.println("Engine started!");
    }
}
  • Pros: Quick and readable.
  • Cons: Field injection is less testable and not recommended for complex applications because the dependency is hard to mock in unit tests.

2. Constructor Injection (Recommended)

Here, dependencies are injected via the constructor. This is the most preferred approach as it promotes immutability and easy testing.

import org.springframework.stereotype.Component;

@Component
public class CarService {

    private final Engine engine;

    @Autowired // Optional in Spring Boot since 4.3+
    public CarService(Engine engine) {
        this.engine = engine;
    }

    public void startCar() {
        engine.start();
    }
}
@Component
public class Engine {
    public void start() {
        System.out.println("Engine started!");
    }
}
  • Pros: Cleaner, better for unit testing, adheres to immutability principles.
  • Cons: Requires explicit constructors, but modern tools (e.g., Lombok) simplify this.

3. Setter Injection

Dependencies are injected via setter methods. This is helpful when a bean property is optional and not required during object creation.

import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Autowired;

@Component
public class CarService {

    private Engine engine;

    @Autowired
    public void setEngine(Engine engine) {
        this.engine = engine;
    }

    public void startCar() {
        engine.start();
    }
}
@Component
public class Engine {
    public void start() {
        System.out.println("Engine started!");
    }
}
  • Pros: Useful when you want to inject optional dependencies.
  • Cons: Makes the class mutable and harder to test.

Behind the Scenes

  1. Bean Discovery: The Spring IoC container detects beans annotated with @Component, @Service, @Repository, @Controller, or those defined explicitly in configuration classes.
  2. Dependency Resolution: When @Autowired is detected on a field, constructor, or setter, Spring scans its ApplicationContext to find a matching bean (by type).
  3. Injection: The resolved bean (dependency) is injected into the target where @Autowired is present.

Handling Ambiguity

If multiple beans of the same type are defined, Spring would throw a NoUniqueBeanDefinitionException. You can resolve this by:

  • Using @Qualifier:
@Autowired
@Qualifier("specificEngine")
private Engine engine;
  • Marking a bean as a primary candidate:
@Primary
@Component
public class Engine { ... }

Example: A Working Spring Boot Application

Here’s a complete example of how @Autowired can be used in a Spring Boot application.

Application Entry Point

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

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

Service & Dependency

import org.springframework.stereotype.Service;

@Service
public class GreetingService {

    public String getGreeting() {
        return "Hello, Dependency Injection!";
    }
}
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class GreetingController {

    private final GreetingService greetingService;

    public GreetingController(GreetingService greetingService) {
        this.greetingService = greetingService;
    }

    @GetMapping("/greet")
    public String greet() {
        return greetingService.getGreeting();
    }
}

Run the application, and visiting http://localhost:8080/greet will give you the message:
Hello, Dependency Injection!

Best Practices

  1. Use Constructor Injection for mandatory dependencies.
  2. Avoid Field Injection, except for small applications or testing purposes.
  3. Annotate beans properly using stereotypes like @Service, @Component, etc.
  4. For optional beans, use @Autowired(required = false) or setter injection.
  5. Handle bean ambiguity with @Qualifier or @Primary.