@GetMapping

Maps HTTP GET requests to a handler method; use it for reads that never change server state.

Signature
@GetMapping("/{id}")
  • Added in Spring Boot Spring Framework 4.3

Parameters

ParameterTypeDescription
$valueoptionalString[]= {}Alias for path. One or more URI patterns such as "/{id}". Combined with any class-level @RequestMapping path.
$pathoptionalString[]= {}The URI patterns this method handles. Same as value; use whichever reads better.
$paramsoptionalString[]= {}Narrows the mapping by query parameters: "type", "!type" or "type=pdf".
$headersoptionalString[]= {}Narrows the mapping by request headers, for example "X-Feature=beta".
$consumesoptionalString[]= {}Media types the request body must have (checked against Content-Type). A mismatch gives 415.
$producesoptionalString[]= {}Media types the method can write (checked against Accept). No overlap gives 406.
$versionoptionalString= ""API version this mapping serves, such as "1.1" or "1.1+". Needs API versioning configured.
$nameoptionalString= ""A name for the mapping, used by MvcUriComponentsBuilder to build links. Rarely needed.

Return value

n/aShortcut for @RequestMapping(method = RequestMethod.GET). The method handles GET requests (and HEAD, which Spring serves from the same method without a body).

Errors

Wrong method on a matching path gives 405 Method Not Allowed with an Allow header.

Changelog

  • PHP Spring Framework 7.0Added the version attribute for first-class API versioning.

@GetMapping is @RequestMapping(method = GET) with less typing. GET is the HTTP method for reading: it must be safe (no side effects the client asked for) and idempotent (repeating it gives the same result). Browsers, proxies, CDNs and HTTP clients rely on that. They prefetch GET links, retry them on network errors and cache the responses.

What it gives you for free

  • HEAD requests are answered from the same method, with headers but no body.
  • OPTIONS requests are answered with an Allow header listing the mapped methods.
  • A POST to a GET-only path gets 405 with an Allow header, without any code from you.
@GetMapping("/{id}")
ResponseEntity<Article> one(@PathVariable long id) {
    return ResponseEntity.of(repository.findById(id));   // 200 or 404
}

Returning not found

Returning null from a GET method gives 200 with an empty body, which clients read as success. Use ResponseEntity.of(optional) for a bare 404, or throw an exception that your error handling maps to a problem detail when the client needs an explanation.

Mistakes people make

  • State changes behind GET. GET /api/orders/42/cancel will be triggered by a link preview, a crawler or a retry. Use POST for actions.
  • Request bodies on GET. HTTP gives a GET body no defined meaning and many proxies drop it. Put filters in query parameters; if the query is too large for a URL, use a POST search endpoint.
  • Unbounded lists. A list endpoint that returns every row works in development and times out in production. Page it from the start.
  • Secrets in query strings. URLs end up in access logs, browser history and Referer headers. Tokens go in headers.

Because GET responses are cacheable, they are also where conditional requests pay off. An ETag lets a client send If-None-Match and receive a bodiless 304 when nothing changed.

Example

Example · java
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

record Article(long id, String title, String status) {}

@RestController
@RequestMapping("/api/articles")
public class ArticleController {

    private final Map<Long, Article> store = new ConcurrentHashMap<>(Map.of(
            1L, new Article(1, "Release notes", "PUBLISHED"),
            2L, new Article(2, "Draft roadmap", "DRAFT")));

    @GetMapping
    List<Article> list(@RequestParam(defaultValue = "PUBLISHED") String status) {
        return store.values().stream().filter(a -> a.status().equals(status)).toList();
    }

    @GetMapping("/{id}")
    ResponseEntity<Article> one(@PathVariable long id) {
        // 200 with the body when present, 404 with no body when empty.
        return ResponseEntity.of(Optional.ofNullable(store.get(id)));
    }
}

When to use it

  • Fetching one order by id for an order details page
  • Listing published articles with a status filter in the query string
  • Exporting a sales report as CSV for a spreadsheet user

Frequently asked questions

Do I need a separate method for HEAD?

No. A @GetMapping method also serves HEAD; Spring runs it and discards the body while keeping headers such as Content-Length.

Can a GET method accept @RequestBody?

Spring will bind one, but HTTP gives a GET body no defined semantics and proxies may strip it. Use query parameters or a POST search endpoint.

More examples

Conditional GET with an ETag

The ETag is derived from the row's version. When a GET returns a ResponseEntity with an ETag, Spring MVC checks If-None-Match itself (weak tags such as W/"7" included) and answers 304 with no body, so you do not compare headers by hand. ShallowEtagHeaderFilter is the blunt alternative: it hashes every response body, which saves bandwidth but not the work of building it.

Example · java
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

record Profile(long id, String displayName, long version) {}

@RestController
public class ProfileController {

    @GetMapping("/api/profiles/{id}")
    ResponseEntity<Profile> profile(@PathVariable long id) {
        Profile profile = new Profile(id, "ada", 7);
        // Spring compares this ETag with the request's If-None-Match header.
        // When they match it sends 304 Not Modified with no body.
        return ResponseEntity.ok().eTag("\"" + profile.version() + "\"").body(profile);
    }
}

One path, two representations

Both methods map the same path and differ by produces. Spring reads the Accept header and calls the matching method; a client asking for text/csv gets the CSV string.

Example · java
import java.util.List;

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

record Sale(String sku, int quantity) {}

@RestController
public class ReportController {

    private final List<Sale> sales = List.of(new Sale("KET-01", 4), new Sale("TOA-02", 9));

    @GetMapping(path = "/api/reports/sales", produces = "application/json")
    List<Sale> json() {
        return sales;
    }

    @GetMapping(path = "/api/reports/sales", produces = "text/csv")
    String csv() {
        StringBuilder out = new StringBuilder("sku,quantity\n");
        sales.forEach(s -> out.append(s.sku()).append(',').append(s.quantity()).append('\n'));
        return out.toString();
    }
}

Several paths for one method

value accepts an array. Mapping the old and new path to the same method keeps existing clients working while you move them over.

Example · java
import java.util.Map;

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

@RestController
public class MeController {

    // Several paths for one method: handy while renaming an endpoint.
    @GetMapping({"/api/me", "/api/users/current"})
    Map<String, String> me() {
        return Map.of("username", "ada");
    }
}

Discussion

  • Be the first to comment on this lesson.