Creating and updating resources is half of every REST API.
For POST, PUT and PATCH, the client sends JSON in the body. Annotate a parameter with @RequestBody and Jackson deserialises it into your type. Records work well as request DTOs: immutable, concise, and the JSON field names match the component names. Unknown JSON properties are ignored by default in Spring Boot; malformed JSON gives 400.
Use separate DTOs for input and output rather than exposing your JPA entities directly. That keeps clients from setting fields like id or createdAt, and lets the API evolve independently of the database.
Status codes matter:
201 Createdwith aLocationheader after creating something:ResponseEntity.created(uri).body(dto).ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}")builds the URI.204 No Contentafter a successful delete or an update that returns nothing:ResponseEntity.noContent().build(), or annotate the method with@ResponseStatus(HttpStatus.NO_CONTENT).404when the target doesn't exist,409for conflicts.
PUT replaces a resource and should be idempotent; PATCH changes part of it. Keep them semantically correct and clients (and caches and retries) behave predictably.
Test quickly with curl -X POST -H 'Content-Type: application/json' -d '{...}' or an HTTP client in your IDE.
Example
record CreateTodo(String title) {}
record Todo(long id, String title, boolean done) {}
@RestController
@RequestMapping("/api/todos")
class TodoController {
private final Map<Long, Todo> store = new ConcurrentHashMap<>();
private final AtomicLong ids = new AtomicLong();
@PostMapping
ResponseEntity<Todo> create(@RequestBody CreateTodo req) {
Todo t = new Todo(ids.incrementAndGet(), req.title(), false);
store.put(t.id(), t);
URI location = ServletUriComponentsBuilder.fromCurrentRequest()
.path("/{id}").buildAndExpand(t.id()).toUri();
return ResponseEntity.created(location).body(t); // 201 + Location
}
@DeleteMapping("/{id}")
ResponseEntity<Void> delete(@PathVariable long id) {
return store.remove(id) != null
? ResponseEntity.noContent().build() // 204
: ResponseEntity.notFound().build(); // 404
}
}
// curl -i -X POST localhost:8080/api/todos -H 'Content-Type: application/json' \
// -d '{"title":"write lesson"}' -> 201, Location: .../api/todos/1Remember
@RequestBodydeserialises JSON into a DTO (records work well)- Use separate request/response DTOs instead of entities
- Return 201 + Location on create, 204 on delete, 404 when missing
Watch out for
- Binding request bodies straight to JPA entities, letting clients overwrite ids or internal fields.