DEV Community

Suman Naskar
Suman Naskar

Posted on

Spring Boot For Beginner

πŸš€ Building a REST API with Java Spring Boot: A Practical Beginner’s Guide

If you're coming from Java and want to move into backend development, Spring Boot is one of the best frameworks to learn.

It removes a lot of the boilerplate traditionally associated with Spring and makes it surprisingly easy to build production-ready REST APIs.

In this article, we'll build a simple Blog REST API using:

  • β˜• Java
  • 🌱 Spring Boot
  • 🌐 Spring Web
  • πŸ—„οΈ Spring Data JPA
  • 🐘 PostgreSQL
  • πŸ“¦ Maven
  • πŸ§ͺ Postman

By the end, we'll have an API that can:

  • Create a blog post
  • Get all blog posts
  • Get a post by ID
  • Update a post
  • Delete a post

1. What is Spring Boot?

Spring Boot is a framework built on top of the Spring Framework that makes it easier to create Java applications.

Without Spring Boot, you often need to configure many things manually.

Spring Boot gives us:

  • Auto-configuration
  • Embedded servers
  • Starter dependencies
  • Production-ready features
  • Easy REST API development

A simple Spring Boot application can be started with:

@SpringBootApplication
public class BlogApplication {

    public static void main(String[] args) {
        SpringApplication.run(BlogApplication.class, args);
    }
}
Enter fullscreen mode Exit fullscreen mode

That's enough to start our application.


2. Create the Spring Boot Project

The easiest way to create a Spring Boot project is through Spring Initializr.

Choose:

Project: Maven
Language: Java
Spring Boot: Latest stable version
Packaging: Jar
Java: 17+
Enter fullscreen mode Exit fullscreen mode

Add these dependencies:

Spring Web
Spring Data JPA
PostgreSQL Driver
Validation
Lombok
Enter fullscreen mode Exit fullscreen mode

Your project structure will look something like:

src
 └── main
     └── java
         └── com.example.blog
             β”œβ”€β”€ BlogApplication.java
             β”œβ”€β”€ controller
             β”œβ”€β”€ service
             β”œβ”€β”€ repository
             β”œβ”€β”€ entity
             └── dto
Enter fullscreen mode Exit fullscreen mode

This separation will become important as our application grows.


3. Create the Blog Entity

Let's create a simple BlogPost entity.

@Entity
@Table(name = "blog_posts")
public class BlogPost {

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

    @NotBlank
    private String title;

    @NotBlank
    @Column(columnDefinition = "TEXT")
    private String content;

    private String author;

    // Getters and setters
}
Enter fullscreen mode Exit fullscreen mode

The @Entity annotation tells JPA that this class represents a database table.

The following:

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Enter fullscreen mode Exit fullscreen mode

means that id will be automatically generated by the database.


4. Create the Repository

Now we need something that can communicate with our database.

Spring Data JPA makes this extremely simple.

@Repository
public interface BlogPostRepository
        extends JpaRepository<BlogPost, Long> {
}
Enter fullscreen mode Exit fullscreen mode

That's it.

We don't need to manually write SQL for basic operations.

Because we extend JpaRepository, we automatically get methods such as:

findAll()
findById()
save()
deleteById()
existsById()
Enter fullscreen mode Exit fullscreen mode

For example:

List<BlogPost> posts = repository.findAll();
Enter fullscreen mode Exit fullscreen mode

Spring Data JPA handles the database interaction for us.


5. Create the Service Layer

It's generally a good idea to keep business logic outside the controller.

Create:

service/BlogPostService.java
Enter fullscreen mode Exit fullscreen mode
@Service
public class BlogPostService {

    private final BlogPostRepository repository;

    public BlogPostService(BlogPostRepository repository) {
        this.repository = repository;
    }

    public List<BlogPost> getAllPosts() {
        return repository.findAll();
    }

    public BlogPost getPostById(Long id) {
        return repository.findById(id)
                .orElseThrow(() ->
                        new RuntimeException("Blog post not found"));
    }

    public BlogPost createPost(BlogPost post) {
        return repository.save(post);
    }

    public BlogPost updatePost(Long id, BlogPost updatedPost) {

        BlogPost existingPost = getPostById(id);

        existingPost.setTitle(updatedPost.getTitle());
        existingPost.setContent(updatedPost.getContent());
        existingPost.setAuthor(updatedPost.getAuthor());

        return repository.save(existingPost);
    }

    public void deletePost(Long id) {

        if (!repository.existsById(id)) {
            throw new RuntimeException("Blog post not found");
        }

        repository.deleteById(id);
    }
}
Enter fullscreen mode Exit fullscreen mode

Now our application has a clean flow:

Controller
    ↓
Service
    ↓
Repository
    ↓
Database
Enter fullscreen mode Exit fullscreen mode

This separation makes the application easier to maintain.


6. Create the REST Controller

Now let's expose our functionality through REST endpoints.

@RestController
@RequestMapping("/api/posts")
public class BlogPostController {

    private final BlogPostService service;

    public BlogPostController(BlogPostService service) {
        this.service = service;
    }

    @GetMapping
    public List<BlogPost> getAllPosts() {
        return service.getAllPosts();
    }

    @GetMapping("/{id}")
    public BlogPost getPost(@PathVariable Long id) {
        return service.getPostById(id);
    }

    @PostMapping
    public BlogPost createPost(@RequestBody BlogPost post) {
        return service.createPost(post);
    }

    @PutMapping("/{id}")
    public BlogPost updatePost(
            @PathVariable Long id,
            @RequestBody BlogPost post) {

        return service.updatePost(id, post);
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deletePost(
            @PathVariable Long id) {

        service.deletePost(id);

        return ResponseEntity.noContent().build();
    }
}
Enter fullscreen mode Exit fullscreen mode

Now we have a complete CRUD API.


7. Configure PostgreSQL

Open:

src/main/resources/application.properties
Enter fullscreen mode Exit fullscreen mode

Add:

spring.datasource.url=jdbc:postgresql://localhost:5432/blogdb
spring.datasource.username=postgres
spring.datasource.password=your_password

spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true

spring.jpa.properties.hibernate.format_sql=true
Enter fullscreen mode Exit fullscreen mode

Create the database:

CREATE DATABASE blogdb;
Enter fullscreen mode Exit fullscreen mode

Start the Spring Boot application.

Hibernate will automatically create the blog_posts table.


8. Test the API

Now let's test our API using Postman.

Create a Blog Post

POST /api/posts
Enter fullscreen mode Exit fullscreen mode

Request body:

{
    "title": "Getting Started with Spring Boot",
    "content": "Spring Boot makes Java backend development much easier.",
    "author": "John"
}
Enter fullscreen mode Exit fullscreen mode

Response:

{
    "id": 1,
    "title": "Getting Started with Spring Boot",
    "content": "Spring Boot makes Java backend development much easier.",
    "author": "John"
}
Enter fullscreen mode Exit fullscreen mode

Get All Posts

GET /api/posts
Enter fullscreen mode Exit fullscreen mode

Response:

[
    {
        "id": 1,
        "title": "Getting Started with Spring Boot",
        "content": "Spring Boot makes Java backend development much easier.",
        "author": "John"
    }
]
Enter fullscreen mode Exit fullscreen mode

Get a Single Post

GET /api/posts/1
Enter fullscreen mode Exit fullscreen mode

Update a Post

PUT /api/posts/1
Enter fullscreen mode Exit fullscreen mode

Request:

{
    "title": "Spring Boot REST API",
    "content": "Building REST APIs with Spring Boot is simple.",
    "author": "John"
}
Enter fullscreen mode Exit fullscreen mode

Delete a Post

DELETE /api/posts/1
Enter fullscreen mode Exit fullscreen mode

If everything is successful, the API returns:

204 No Content
Enter fullscreen mode Exit fullscreen mode

9. Understanding the Architecture

At this point, we have a basic working backend.

The request flow looks like this:

                Client
                  β”‚
                  β–Ό
           REST Controller
                  β”‚
                  β–Ό
              Service
                  β”‚
                  β–Ό
             Repository
                  β”‚
                  β–Ό
              PostgreSQL
Enter fullscreen mode Exit fullscreen mode

For example:

POST /api/posts
       ↓
BlogPostController
       ↓
BlogPostService
       ↓
BlogPostRepository
       ↓
PostgreSQL
Enter fullscreen mode Exit fullscreen mode

This architecture is commonly used in Spring Boot applications.


10. Why Not Put Everything in the Controller?

You might wonder:

Why do we need a service layer?

Technically, we could put everything inside the controller.

For a tiny project, that might work.

But imagine the application eventually contains:

User
Blog
Comment
Like
Notification
Authentication
Payment
Enter fullscreen mode Exit fullscreen mode

If all business logic lives inside controllers, they quickly become huge and difficult to maintain.

A better separation is:

Controller β†’ Handles HTTP requests

Service β†’ Handles business logic

Repository β†’ Handles database operations

Entity β†’ Represents database data
Enter fullscreen mode Exit fullscreen mode

This makes the application easier to test and extend.


11. Improve Error Handling

Our current implementation uses:

throw new RuntimeException("Blog post not found");
Enter fullscreen mode Exit fullscreen mode

That's not ideal for a production API.

Instead, we can create a custom exception.

public class ResourceNotFoundException
        extends RuntimeException {

    public ResourceNotFoundException(String message) {
        super(message);
    }
}
Enter fullscreen mode Exit fullscreen mode

Then:

throw new ResourceNotFoundException(
        "Blog post not found with id: " + id
);
Enter fullscreen mode Exit fullscreen mode

We can handle it globally:

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<String> handleNotFound(
            ResourceNotFoundException exception) {

        return ResponseEntity
                .status(HttpStatus.NOT_FOUND)
                .body(exception.getMessage());
    }
}
Enter fullscreen mode Exit fullscreen mode

Now the API can return a proper:

404 NOT FOUND
Enter fullscreen mode Exit fullscreen mode

instead of an unexpected server error.


12. Add Validation

We already added:

@NotBlank
Enter fullscreen mode Exit fullscreen mode

to our entity.

But we also need to tell Spring to validate the request.

@PostMapping
public BlogPost createPost(
        @Valid @RequestBody BlogPost post) {

    return service.createPost(post);
}
Enter fullscreen mode Exit fullscreen mode

Now this request:

{
    "title": "",
    "content": "",
    "author": "John"
}
Enter fullscreen mode Exit fullscreen mode

will fail validation.


13. Use DTOs in Real Applications

For learning, accepting the entity directly is fine.

But in production applications, it's generally better to use DTOs.

For example:

public class CreateBlogPostRequest {

    @NotBlank
    private String title;

    @NotBlank
    private String content;

    @NotBlank
    private String author;
}
Enter fullscreen mode Exit fullscreen mode

Then:

@PostMapping
public BlogPost createPost(
        @Valid @RequestBody CreateBlogPostRequest request) {

    return service.createPost(request);
}
Enter fullscreen mode Exit fullscreen mode

Why?

Because your database entity and API contract don't necessarily need to be the same.

DTOs provide a layer between your API and database model.


Top comments (0)