EthicsComputerCourses
Join training +91 90532 10052

Your First REST Controller

Spring Boot · Lesson 8 of 29 Intermediate 7 min read Updated

A REST API is a set of URLs that other programs call over HTTP to read and change data. A mobile app asks GET /api/players and receives JSON; a website sends POST /api/players with JSON to add one. Most Spring Boot projects in industry are exactly this: an API that a React, Angular or Android front end talks to.

In this lesson you will write a controller that returns text and JSON, see what Spring sends over the wire, and understand how a request travels from Tomcat to your method.

REST in one table

REST (Representational State Transfer) is a style, not a library. Its main ideas are that URLs name resources (things, as nouns) and HTTP methods say what to do with them:

Method and URLMeaningTypical success status
GET /api/playersList players200 OK
GET /api/players/3Read player 3200 OK
POST /api/playersCreate a player201 Created
PUT /api/players/3Replace player 3200 OK
DELETE /api/players/3Delete player 3204 No Content

Data travels as JSON (JavaScript Object Notation), a text format every language can read. You will build all five endpoints over the next three lessons.

A controller that returns text and JSON

Java
package in.cricket.academy.hello;

import java.util.List;

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

@RestController
public class HelloController {

    @GetMapping("/hello")
    public String hello() {
        return "Namaste from Pune Cricket Academy!";
    }

    @GetMapping("/academy")
    public Academy academy() {
        return new Academy("Pune Cricket Academy", "Pune", 1299, List.of("Under-14", "Under-19"));
    }

    public record Academy(String name, String city, int monthlyFee, List<String> batches) {}
}
  • @RestController marks the class as a web controller whose method return values are written directly into the HTTP response body. It is @Controller plus @ResponseBody.
  • @GetMapping("/hello") maps GET /hello to the method. There are matching @PostMapping, @PutMapping, @PatchMapping and @DeleteMapping annotations.
  • A String return value is sent as plain text. Any other object, here a record, is converted to JSON by Jackson, the JSON library that the web starter includes.

Restart the app and call both endpoints. The -i option makes curl show the status line and headers too:

Terminal
curl -i http://localhost:8080/hello
Output
HTTP/1.1 200 
Content-Type: text/plain;charset=UTF-8
Content-Length: 34
Date: Sun, 01 Nov 2026 18:06:15 GMT

Namaste from Pune Cricket Academy!
Terminal
curl http://localhost:8080/academy
Output
{"name":"Pune Cricket Academy","city":"Pune","monthlyFee":1299,"batches":["Under-14","Under-19"]}

Jackson used the record's component names as JSON keys and turned the List into a JSON array. You wrote no conversion code. Tomcat 11 leaves out the reason phrase ("OK") after the status code; the number is what matters.

How a request reaches your method

  1. Tomcat receives the HTTP request on port 8080.
  2. It hands the request to Spring's DispatcherServlet, the single front door of every Spring MVC application.
  3. The DispatcherServlet asks the handler mappings which method matches GET /hello. They were built at startup by scanning every @RestController.
  4. Your method runs and returns a value.
  5. An HTTP message converter turns the value into bytes: the string converter for String, the Jackson converter for objects. It also sets Content-Type.
  6. Tomcat sends the response.

If no mapping matches, Spring answers with its built-in error response:

Terminal
curl -i http://localhost:8080/helo
Output
HTTP/1.1 404 
Content-Type: application/json
Transfer-Encoding: chunked
Date: Sun, 01 Nov 2026 18:06:20 GMT

{"timestamp":"2026-11-01T18:06:20.521Z","status":404,"error":"Not Found","path":"/helo"}

This JSON comes from Spring Boot's /error handling. In Exception Handling with ProblemDetail you will replace it with a standard format.

A controller for a resource

Real controllers group the endpoints for one resource under a common path with a class-level @RequestMapping, and get their data from a service through constructor injection:

Java
package in.cricket.academy.player;

import java.util.List;

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

@RestController
@RequestMapping("/api/players")
public class PlayerController {

    private final PlayerService service;

    public PlayerController(PlayerService service) {
        this.service = service;
    }

    @GetMapping
    public List<Player> list() {
        return service.findAll();
    }
}

Player is a record (long id, String name, PlayerRole role, int age, String city, LocalDate joinedOn), and PlayerService keeps four sample players in a ConcurrentHashMap for now (a thread-safe map, because many requests run at the same time). Later lessons replace the map with a database without changing the controller.

Terminal
curl http://localhost:8080/api/players
Output
[{"id":1,"name":"Aarav Patil","role":"BATTER","age":15,"city":"Pune","joinedOn":"2025-06-02"},{"id":2,"name":"Priya Deshmukh","role":"BOWLER","age":14,"city":"Pune","joinedOn":"2025-06-09"},{"id":3,"name":"Kabir Shaikh","role":"ALL_ROUNDER","age":16,"city":"Mumbai","joinedOn":"2025-07-14"},{"id":4,"name":"Meera Joshi","role":"WICKET_KEEPER","age":13,"city":"Nashik","joinedOn":"2026-01-05"}]

Notice two Jackson defaults: an enum becomes its name ("BOWLER"), and a LocalDate becomes an ISO date string ("2025-06-09").

@Controller versus @RestController

@Controller is the older, more general annotation. Its methods return the name of a view (an HTML template) unless you add @ResponseBody. @RestController saves you from writing @ResponseBody on every method. Use @RestController for APIs and @Controller for server-rendered HTML pages, covered in the Thymeleaf lesson.

Common mistakes

  • Using @Controller for an API. Spring then looks for a template called "Namaste from..." and fails with an error about a missing view. Use @RestController.
  • Returning a class without getters or record components. Jackson serialises public getters and record components. A class with only private fields becomes {} or fails.
  • Putting business logic in controllers. It cannot be reused or unit-tested easily; move it to a @Service.
  • Forgetting to restart. Without DevTools, code changes need a restart of the application before curl sees them.

Key points

  • @RestController = @Controller + @ResponseBody: return values become the response body.
  • @GetMapping, @PostMapping, @PutMapping, @PatchMapping and @DeleteMapping map HTTP methods and paths to methods.
  • Strings are sent as text/plain; records and other objects become JSON through Jackson.
  • The DispatcherServlet routes each request to the matching method; unmatched URLs get Spring Boot's 404 error JSON.
  • Use a class-level @RequestMapping("/api/...") per resource and keep controllers thin.

Exercise

Add a GET /api/batches endpoint that returns a JSON array of batch objects for the academy. Each batch has a name, an ageLimit and a timing, for example Under-14 at "6:00 AM" and Under-19 at "7:30 AM". Use a record, write the controller, and show the expected curl output.

Show solution
Java
@RestController
@RequestMapping("/api/batches")
public class BatchController {

    public record Batch(String name, int ageLimit, String timing) {}

    @GetMapping
    public List<Batch> list() {
        return List.of(
                new Batch("Under-14", 14, "6:00 AM"),
                new Batch("Under-19", 19, "7:30 AM"));
    }
}
Terminal
curl http://localhost:8080/api/batches
Output
[{"name":"Under-14","ageLimit":14,"timing":"6:00 AM"},{"name":"Under-19","ageLimit":19,"timing":"7:30 AM"}]

Jackson writes the record components in declaration order, and the list becomes a JSON array.

Quick Quiz

1. What does @RestController combine?

2. A controller method returns a record. What does the client receive?

3. Which Spring component receives every request first and routes it to a controller method?

4. Which HTTP method and status fit "create a new player"?

Learn Spring Boot with a mentor

Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call