← Java

Java · 13 / 18

Spring Boot REST with validation

Receive JSON, validate input and return an appropriate HTTP status.

  • Medium
  • Intermediate
  • 45 min

Prerequisites: JDBC, SQLite and local transactions

A REST controller connects HTTP to the application core. It should not contain all domain rules. DTOs describe data at the transport boundary. Bean Validation checks basic input requirements. The learning project accepts a title and returns a normalised value. It has no durable storage and is not a copy of the protected PDF server API.

01Start the server project

Save both files in a new folder. mvn spring-boot:run starts on port 8282. Spring Boot 4.1 and Java 21 match the checked PDF codebase. The learning server remains local.

pom.xml · xmlOpen file
<project xmlns="http://maven.apache.org/POM/4.0.0">
  <modelVersion>4.0.0</modelVersion>
  <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>4.1.0</version></parent>
  <groupId>example</groupId><artifactId>rest-learning</artifactId><version>1.0</version>
  <properties><java.version>21</java.version></properties>
  <dependencies>
    <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>
    <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency>
  </dependencies>
  <build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
</project>

Run / check

mvn spring-boot:run -Dspring-boot.run.arguments=--server.port=8282

02Read the DTO and controller

A record groups immutable input data. @RequestBody reads JSON. @Valid enables @NotBlank validation. @PostMapping names the route. ResponseEntity sets the HTTP status and response.

src/main/java/tutorial/Application.java · javaOpen file
package tutorial;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@SpringBootApplication
@RestController
public class Application {
  record Input(@NotBlank String title) {}
  record Output(String title) {}
  @PostMapping("/normalise")
  ResponseEntity<Output> normalise(@Valid @RequestBody Input input) {
    return ResponseEntity.ok(new Output(input.title().trim()));
  }
  public static void main(String[] args) { SpringApplication.run(Application.class, args); }
}

03Send a real request

Run the curl commands in a second terminal. The valid request should return 200. Whitespace-only input returns 400. The response normalises text but intentionally creates no persisted resource. We therefore do not use 201 Created here.

requests.sh · bashOpen file
curl -i -H 'Content-Type: application/json' -d '{"title":"  Letters  "}' http://127.0.0.1:8282/normalise
curl -i -H 'Content-Type: application/json' -d '{"title":"   "}' http://127.0.0.1:8282/normalise

04Add the domain boundary

In the real PDF project, JobRestController delegates to JobApplicationService. The service checks states, limits and storage workflows. Transport validation complements those rules rather than replacing them. Security and upload ownership are handled separately in the full server.

Expected result

A valid title is normalised. A blank title produces HTTP 400.

Your exercise

Add a maximum length of 60 and test exactly 60 and 61 characters.

Show the solution and explanation

Also use @Size(max = 60) on title. Consider whether the domain length applies before or after trim. If it concerns normalised text, the service must check it after normalisation.

Project context and sources

The example is simplified for learning. The linked source version shows its use in the project and the checks applied there.