jypi
  • Explore
ChatWays to LearnMind mapAbout

jypi

  • About Us
  • Our Mission
  • Team
  • Careers

Resources

  • Ways to Learn
  • Mind map
  • Blog
  • Help Center
  • Community Guidelines
  • Contributor Guide

Legal

  • Terms of Service
  • Privacy Policy
  • Cookie Policy
  • Content Policy

Connect

  • Twitter
  • Discord
  • Instagram
  • Contact Us
jypi

© 2026 jypi. All rights reserved.

Java Spring Boot Microservices Bootcamp
Chapters

1Introduction to Microservices Architecture

2Getting Started with Spring Boot

3Building RESTful APIs with Spring Boot

REST Architecture PrinciplesCreating REST ControllersPath Variables and Query Parameters
Courses/Java Spring Boot Microservices Bootcamp/Building RESTful APIs with Spring Boot

Building RESTful APIs with Spring Boot

553 views

Explore the creation of RESTful services using Spring Boot.

Content

2 of 3

Creating REST Controllers

Controllers That Dont Ghost You
173 views
intermediate
humorous
software engineering
visual
gpt-5
173 views

Versions:

Controllers That Dont Ghost You

Watch & Learn

AI-discovered learning video

YouTube

Start learning for free

Sign up to save progress, unlock study materials, and track your learning.

  • Bookmark content and pick up later
  • AI-generated study materials
  • Flashcards, timelines, and more
  • Progress tracking and certificates

Free to join · No credit card required

Creating REST Controllers in Spring Boot

You set up your Spring Boot project. You nodded solemnly at REST architecture like it was a TED Talk. Now its time to make the app actually talk back. Welcome to creating REST controllers  the front door of your microservice, the bouncer at Club API, the person who decides whether your HTTP request gets in or gets ejected with a 404.

If REST is the city plan, a REST controller is the street-level shop where the business actually happens.

Well build on what you already know: resources, HTTP methods, and statelessness. Lets wire the principles to real endpoints without summoning any accidental 500s.


What Is a REST Controller?

A REST controller in Spring Boot is a class that:

  • Handles incoming HTTP requests (GET, POST, PUT, PATCH, DELETE)
  • Maps them to Java methods using annotations
  • Returns resource representations (usually JSON)
  • Sets appropriate HTTP status codes and headers

In Spring, youll meet two very similar-sounding annotations. Only one will text you back.

Annotation What it does Typical use
@Controller MVC controller for server-side views (e.g., Thymeleaf) Traditional web apps
@RestController Combines @Controller + @ResponseBody, so method return values are written directly to the HTTP response as JSON RESTful APIs

TL;DR: For REST APIs, use @RestController. Unless youre rendering HTML like its 2011, in which case, live your truth.


How Does a REST Controller Work?

  • You annotate methods with @GetMapping, @PostMapping, etc.
  • Springs HTTP message converters (hello, Jackson!) serialize/deserialize JSON automatically.
  • You return a body (or not), and Spring takes care of status codes and content type. Or you take control with ResponseEntity.

Remember from REST principles:

  • Resources are nouns (e.g., /coffees, /users).
  • HTTP verbs do the action.
  • Idempotency matters: GET/PUT/DELETE are idempotent; POST is not.

Examples of Creating REST Controllers (with Coffee, obviously)

Lets build a tiny REST controller for a Coffee resource. Well keep layers clean and vibes cleaner: a CoffeeController talks to a CoffeeService, returns DTOs, and plays nice with status codes.

Request and Response DTOs

package com.example.coffee.api;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Positive;
import java.util.UUID;

public record CoffeeRequest(
    @NotBlank String name,
    @Positive Integer sizeOunces
) {}

public record CoffeeResponse(
    UUID id,
    String name,
    Integer sizeOunces
) {}

The Service Contract (because controllers shouldnt brew coffee directly)

package com.example.coffee.domain;

import com.example.coffee.api.CoffeeRequest;
import com.example.coffee.api.CoffeeResponse;
import java.util.List;
import java.util.Map;
import java.util.UUID;

public interface CoffeeService {
    List<CoffeeResponse> findAll(int page, int size);
    CoffeeResponse findById(UUID id);
    CoffeeResponse create(CoffeeRequest request);
    CoffeeResponse replace(UUID id, CoffeeRequest request);
    CoffeeResponse updatePartial(UUID id, Map<String, Object> fields);
    void delete(UUID id);
}

The REST Controller

package com.example.coffee.api;

import com.example.coffee.domain.CoffeeService;
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.util.UriComponentsBuilder;

import java.net.URI;
import java.util.List;
import java.util.Map;
import java.util.UUID;

@RestController
@RequestMapping("/api/v1/coffees")
public class CoffeeController {

    private final CoffeeService service;

    public CoffeeController(CoffeeService service) {
        this.service = service;
    }

    // GET /coffees?page=0&size=20
    @GetMapping
    public List<CoffeeResponse> list(
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "20") int size) {
        return service.findAll(page, size);
    }

    // GET /coffees/{id}
    @GetMapping("/{id}")
    public CoffeeResponse get(@PathVariable UUID id) {
        return service.findById(id);
    }

    // POST /coffees
    @PostMapping
    public ResponseEntity<CoffeeResponse> create(
            @Valid @RequestBody CoffeeRequest request,
            UriComponentsBuilder uriBuilder) {
        CoffeeResponse created = service.create(request);
        URI location = uriBuilder.path("/api/v1/coffees/{id}")
                .buildAndExpand(created.id()).toUri();
        return ResponseEntity.created(location).body(created);
    }

    // PUT /coffees/{id}
    @PutMapping("/{id}")
    public CoffeeResponse replace(
            @PathVariable UUID id,
            @Valid @RequestBody CoffeeRequest request) {
        return service.replace(id, request);
    }

    // PATCH /coffees/{id}
    @PatchMapping("/{id}")
    public CoffeeResponse updatePartial(
            @PathVariable UUID id,
            @RequestBody Map<String, Object> fields) {
        return service.updatePartial(id, fields);
    }

    // DELETE /coffees/{id}
    @DeleteMapping("/{id}")
    @ResponseStatus(org.springframework.http.HttpStatus.NO_CONTENT)
    public void delete(@PathVariable UUID id) {
        service.delete(id);
    }
}

Key moments:

  • @RestController ensures JSON out-of-the-box.
  • @RequestMapping("/api/v1/coffees") scopes all endpoints cleanly.
  • @Valid @RequestBody activates validation for input.
  • ResponseEntity.created(location) returns 201 and a Location header like a true REST citizen.

201 + Location is the API equivalent of cHeres your table, right this way.d


Why Does Validation and Error Handling Matter?

Because 1 invalid input without clear errors is how you get 1-star reviews from the QA team.

Centralized Exception Handling

package com.example.coffee.api;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.time.Instant;
import java.util.HashMap;
import java.util.Map;

@RestControllerAdvice
public class ApiExceptionHandler {

    @ExceptionHandler(NotFoundException.class)
    public ResponseEntity<Map<String, Object>> handleNotFound(NotFoundException ex) {
        Map<String, Object> body = new HashMap<>();
        body.put("timestamp", Instant.now().toString());
        body.put("status", 404);
        body.put("error", "Not Found");
        body.put("message", ex.getMessage());
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(body);
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<Map<String, Object>> handleValidation(MethodArgumentNotValidException ex) {
        Map<String, Object> body = new HashMap<>();
        body.put("timestamp", Instant.now().toString());
        body.put("status", 400);
        body.put("error", "Bad Request");
        body.put("message", "Validation failed");
        body.put("fields", ex.getBindingResult().getFieldErrors().stream()
                .collect(java.util.stream.Collectors.toMap(
                        err -> err.getField(),
                        err -> err.getDefaultMessage(),
                        (a, b) -> a)));
        return ResponseEntity.badRequest().body(body);
    }
}

And a simple custom exception:

package com.example.coffee.api;

public class NotFoundException extends RuntimeException {
    public NotFoundException(String message) { super(message); }
}

With this, you avoid spraying try/catch blocks inside your controllers. Your responses are consistent and helpful.


HTTP Method Mapping Cheatsheet (a gentle nudge from REST school)

Mapping Semantics Idempotent Typical Response
@GetMapping Read resource(s) Yes 200 OK (body) or 404 if missing
@PostMapping Create new resource No 201 Created + Location header
@PutMapping Replace entire resource Yes 200 OK or 204 No Content
@PatchMapping Partial update Not guaranteed 200 OK (body)
@DeleteMapping Remove resource Yes 204 No Content (if existed)

If youre updating a single field with PUT, somewhere a PATCH RFC sighs heavily.


Common Mistakes in Creating REST Controllers

  • Returning 200 for a create operation. Use 201 + Location when you POST.
  • Forgetting @RequestBody on JSON input, resulting in mysterious 415 Unsupported Media Type or null fields.
  • Mixing view controllers and REST controllers. Keep @Controller and @RestController lanes separate.
  • Leaking domain exceptions as 500s. Map them intentionally (e.g., 404 for not found, 409 for conflicts).
  • Ignoring validation. Add @Valid and return field errors like a civilized API.
  • Designing verbs in URIs (/createCoffee). Use nouns and HTTP methods (/coffees).
  • Returning entities directly. Prefer DTOs to avoid overexposing internals and to control your API contract.
  • Skipping pagination for list endpoints. Unbounded lists are fun until prod meets 1M rows.

Pro Tips That Make You Look Unreasonably Good

  • Content negotiation is automatic. Still, you can be explicit: @GetMapping(produces = "application/json").
  • Use ResponseEntity when you need surgical control over status or headers.
  • Keep paths kebab-case and plural: /api/v1/coffees not /api/v1/CoffeeList.
  • Traceability: add correlation IDs via filters; your future debugging self will weep with gratitude.
  • For PATCH, consider JSON Merge Patch or JSON Patch formats for clarity and safety.

Micro-Quest: Can You Spot Better Design?

Imagine this in your everyday life: you order a latte (POST), check your order status (GET), change your milk option (PATCH), or reorder the same thing (not idempotent unless your cafe is weird). Why do people keep misunderstanding this? Because we love verbs in URLs. Resist. Let the HTTP method do the talking.

Clean REST is like good UI: when its done right, nobody notices 1 they just get what they need without friction.


Quick Setup Reminder (because were building on earlier steps)

You already:

  1. Created a Spring Boot app (starter-web brings MVC + Jackson).
  2. Understood REST principles (resources, idempotency, status codes).

Now you:

  • Add a controller class under a component-scanned package.
  • Define mappings with clear URIs.
  • Return DTOs with correct status codes.
  • Centralize error handling with @RestControllerAdvice.

Summary: Creating REST Controllers Without Chaos

Creating REST controllers in Spring Boot is where architecture meets reality. You map clean URIs to clear methods, honor HTTP semantics, validate inputs, and send back JSON like a pro. Keep @RestController for APIs, use DTOs, return the right status codes, and handle errors like you meant it.

Key takeaways:

  • Controllers translate HTTP into your domain and back to JSON.
  • @RestController + mapping annotations give you fast, clean endpoints.
  • ResponseEntity and @RestControllerAdvice give you control and consistency.
  • REST isnt just a vibe; its a contract. Follow it, and clients will love you.

Next up, youll wire this to persistence or call out to other microservices and really test those REST muscles. For now? Youve got the front door built. Its sturdy, stylish, and it returns 201 with a Location header. Chefs kiss.

Flashcards
Mind Map
Speed Challenge

Comments (0)

Please sign in to leave a comment.

No comments yet. Be the first to comment!

Ready to practice?

Sign up now to study with flashcards, practice questions, and more — and track your progress on this topic.

Study with flashcards, timelines, and more
Earn certificates for completed courses
Bookmark content for later reference
Track your progress across all topics