π 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);
}
}
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+
Add these dependencies:
Spring Web
Spring Data JPA
PostgreSQL Driver
Validation
Lombok
Your project structure will look something like:
src
βββ main
βββ java
βββ com.example.blog
βββ BlogApplication.java
βββ controller
βββ service
βββ repository
βββ entity
βββ dto
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
}
The @Entity annotation tells JPA that this class represents a database table.
The following:
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
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> {
}
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()
For example:
List<BlogPost> posts = repository.findAll();
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
@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);
}
}
Now our application has a clean flow:
Controller
β
Service
β
Repository
β
Database
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();
}
}
Now we have a complete CRUD API.
7. Configure PostgreSQL
Open:
src/main/resources/application.properties
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
Create the database:
CREATE DATABASE blogdb;
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
Request body:
{
"title": "Getting Started with Spring Boot",
"content": "Spring Boot makes Java backend development much easier.",
"author": "John"
}
Response:
{
"id": 1,
"title": "Getting Started with Spring Boot",
"content": "Spring Boot makes Java backend development much easier.",
"author": "John"
}
Get All Posts
GET /api/posts
Response:
[
{
"id": 1,
"title": "Getting Started with Spring Boot",
"content": "Spring Boot makes Java backend development much easier.",
"author": "John"
}
]
Get a Single Post
GET /api/posts/1
Update a Post
PUT /api/posts/1
Request:
{
"title": "Spring Boot REST API",
"content": "Building REST APIs with Spring Boot is simple.",
"author": "John"
}
Delete a Post
DELETE /api/posts/1
If everything is successful, the API returns:
204 No Content
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
For example:
POST /api/posts
β
BlogPostController
β
BlogPostService
β
BlogPostRepository
β
PostgreSQL
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
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
This makes the application easier to test and extend.
11. Improve Error Handling
Our current implementation uses:
throw new RuntimeException("Blog post not found");
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);
}
}
Then:
throw new ResourceNotFoundException(
"Blog post not found with id: " + id
);
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());
}
}
Now the API can return a proper:
404 NOT FOUND
instead of an unexpected server error.
12. Add Validation
We already added:
@NotBlank
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);
}
Now this request:
{
"title": "",
"content": "",
"author": "John"
}
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;
}
Then:
@PostMapping
public BlogPost createPost(
@Valid @RequestBody CreateBlogPostRequest request) {
return service.createPost(request);
}
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)