GreetingService.GreetingService
public GreetingService(GreetingProperties properties) {
A first Spring Boot web app with Maven, @SpringBootApplication, @RestController, query parameters, JSON POST requests, configuration properties, service validation, error handling, and tests.
scripts/run.sh lesson_6 demo
scripts/run.sh lesson_6 exercise
scripts/apply_answers.sh lesson_6
scripts/run.sh lesson_6 verify
scripts/reset_exercises.sh lesson_6
45 pages generated for Java functions in this lesson.
This lesson starts a Spring Boot tutorial from the smallest useful web app: a GET /hello endpoint that returns JSON. It then extends the hello-world example with query parameters, a JSON POST, configuration properties, service-layer validation, error handling, and tests.
The lesson uses Spring Boot 4.1.0, Java 21, and Maven. Spring Boot's official project page describes Boot as a way to create stand-alone Spring applications with embedded servers and starter dependencies. The official system requirements for Spring Boot 4.1.0 require Java 17 or newer and Maven 3.6.3 or newer.
References:
| Minutes | Activity |
|---|---|
| 0-10 | Explain what Spring Boot adds to plain Java: application context, auto-configuration, embedded server, starters, and runnable app packaging. |
| 10-25 | Run the exercise stub and the demo. Compare command-line object calls with HTTP JSON calls. |
| 25-45 | Read SpringHelloApplication, HelloController, and GreetingConfiguration to trace how a request reaches a service. |
| 45-65 | Study GreetingService as the learner-editable contract. Identify name defaults, whitespace handling, punctuation rules, and max-length validation. |
| 65-95 | Implement GreetingService.hello, normalizeName, and normalizePunctuation. |
| 95-110 | Apply answers and run the Maven verifier. Read each failing test as a Spring Boot behavior requirement. |
| 110-120 | Start the app on local port 8080 and call it with curl or a browser. |
Run the safe stub exercise:
scripts/run.sh lesson_6 exercise
Run a real embedded server demo that starts on a random port, calls itself, and exits:
scripts/run.sh lesson_6 demo
Apply answers and verify:
scripts/apply_answers.sh lesson_6
scripts/run.sh lesson_6 verify
scripts/reset_exercises.sh lesson_6
Start the real hello app on local port 8080:
scripts/serve_spring_hello_8080.sh
The default bind address is 0.0.0.0. To bind only to loopback:
SERVER_ADDRESS=127.0.0.1 SERVER_PORT=8080 scripts/serve_spring_hello_8080.sh
Try these requests:
curl http://127.0.0.1:8080/
curl 'http://127.0.0.1:8080/hello?name=Ada'
curl 'http://127.0.0.1:8080/hello?name=Linus&punctuation=%3F'
curl -X POST http://127.0.0.1:8080/hello \
-H 'Content-Type: application/json' \
-d '{"name":"Grace Hopper","punctuation":"."}'
curl 'http://127.0.0.1:8080/hello?name=Ada&punctuation=%40'
Edit:
lesson_6/src/exercise/java/tutorial/lesson6/hello/GreetingService.java
Functions to implement:
hellonormalizeNamenormalizePunctuationThe lesson compiles before implementation. The stub always returns the classic Hello, World! shape. The answer verifier checks defaults, trimming, whitespace collapse, punctuation validation, max-name length, GET /hello, POST /hello, and HTTP 400 error handling.
This workbook is designed to take about two hours. The goal is to understand the first Spring Boot path from main to HTTP request to JSON response, while still practicing normal Java service design.
@SpringBootApplication marks the class Spring Boot starts from.SpringApplication.run(...) creates the application context and starts the embedded web server.spring-boot-starter-web brings in Spring MVC, JSON handling, and an embedded server.@RestController turns return values into HTTP response bodies.@GetMapping and @PostMapping map Java methods to HTTP routes.@RequestParam reads query-string values.@RequestBody reads JSON request bodies.@ConfigurationProperties binds values from application.properties.@Bean tells Spring how to construct an object it should manage.Plain Java starts with:
public static void main(String[] args)
Spring Boot still starts there, but SpringApplication.run does extra work:
Open pom.xml. Find:
spring-boot-starter-parentspring-boot-starter-webspring-boot-starter-testsrc/exercise/java as the Maven source directoryRun:
scripts/reset_exercises.sh lesson_6
scripts/run.sh lesson_6 exercise
scripts/run.sh lesson_6 demo
The exercise runner calls Java objects directly. The demo starts a real embedded server on a random port, sends HTTP requests to it, prints the JSON, and shuts down.
Write down the difference between:
GreetingService.hello(...) directly/hello?name=Ada/helloOpen these files in order:
SpringHelloApplication.javaGreetingConfiguration.javaHelloController.javaGreetingService.javaGreetingResponse.javaGreetingRequest.javaApiExceptionHandler.javaTrace this request:
GET /hello?name=Ada&punctuation=.
Answer these questions:
/hello?name from the query string?GreetingService is the exercise file. Its rules are intentionally small:
tutorial.greeting.max-name-length are rejected.!.!, ?, or ..<prefix>, <name><punctuation>.Examples:
| Input | Expected message |
|---|---|
name="", punctuation="" | Hello, World! |
name="Ada", punctuation="?" | Hello, Ada? |
name=" Grace Hopper ", punctuation="." | Hello, Grace Hopper. |
name="Ada", punctuation="@" | HTTP 400 through the controller |
Edit GreetingService.java.
Round 1: normalizeName
replaceAll("\\s+", " ").properties.defaultName() when the result is blank.properties.maxNameLength().Round 2: normalizePunctuation
!.!, ?, and ..IllegalArgumentException for anything else.Round 3: hello
new GreetingResponse(...).properties.prefix() instead of hard-coding Hello.Run after each round:
scripts/run.sh lesson_6 exercise
Run:
scripts/apply_answers.sh lesson_6
scripts/run.sh lesson_6 verify
scripts/reset_exercises.sh lesson_6
The verifier is a Maven test suite. Read test names as requirements:
buildsDefaultGreetingWhenNameIsMissingtrimsAndCollapsesNameWhitespaceacceptsQuestionMarkPunctuationrejectsUnsupportedPunctuationrejectsNamesLongerThanConfigurationAllowsgetHelloEndpointReturnsJsonGreetingpostHelloEndpointUsesSameServiceRulesbadPunctuationReturnsHttpBadRequestsimpleHealthEndpointReturnsUpStart the app:
scripts/serve_spring_hello_8080.sh
In another terminal:
curl http://127.0.0.1:8080/
curl 'http://127.0.0.1:8080/hello?name=Ada'
curl 'http://127.0.0.1:8080/hello?name=Linus&punctuation=%3F'
curl -X POST http://127.0.0.1:8080/hello \
-H 'Content-Type: application/json' \
-d '{"name":"Grace Hopper","punctuation":"."}'
curl 'http://127.0.0.1:8080/hello?name=Ada&punctuation=%40'
Stop the app with Ctrl+C.
Before leaving the lesson, predict each result:
GET /helloGET /hello?name=%20%20GET /hello?name=Alan%20%20%20TuringGET /hello?punctuation=.GET /hello?punctuation=%3FGET /hello?punctuation=!!GET /hello?name= followed by 41 lettersPOST /hello with {} as the bodyPOST /hello with {"name":null,"punctuation":null}Then run the request and compare.
This is the first framework lesson. Keep the frame tight: Spring Boot does not replace Java fundamentals. It wires Java objects together, starts infrastructure, and maps external requests onto ordinary methods.
pom.xml is part of the lesson. Learners should see how starter dependencies enter the project.SpringHelloApplication.main is still plain Java's entry point.@RestController is a routing adapter, not the place for business rules.GreetingService is deliberately testable without Spring.ApiExceptionHandler keeps error translation separate from greeting logic.scripts/run.sh lesson_6 demo starts a real server on a random port, so it is safe during workshops.scripts/serve_spring_hello_8080.sh is the manual local server path and binds to 0.0.0.0 by default.scripts/run.sh lesson_6 exercise and point out that no web server is required to call service code.scripts/run.sh lesson_6 demo and point out the JSON response bodies.SpringHelloApplication and explain the Boot startup boundary.HelloController and trace only one route first: GET /hello.GreetingService and ask learners what edge cases are missing from the stub.normalizeName live if the class needs one example.normalizePunctuation and hello.scripts/run.sh lesson_6 verify.scripts/serve_spring_hello_8080.sh and make real curl requests.@RequestParam(defaultValue = "!") to solve all defaults. It handles absent query params, but service defaults still matter for direct calls and JSON requests.javac. Spring dependencies require a build tool to resolve libraries and run tests consistently.GET /hello/{name} with @PathVariable.tutorial.greeting.prefix=Ahoy override and verify the message changes.GET /goodbye using the same service pattern./health/simple with /actuator/health.POST /hello.scripts/run.sh lesson_6 exercise first to isolate Java service behavior.SERVER_PORT=8081 scripts/serve_spring_hello_8080.sh.public GreetingService(GreetingProperties properties) {
public GreetingResponse hello(String rawName, String rawPunctuation) {
public String normalizeName(String rawName) {
public String normalizePunctuation(String rawPunctuation) {
public ResponseEntity<ErrorResponse> badRequest(IllegalArgumentException exception) {
private BootHelloDemo() {
public static void main(String[] args) throws IOException, InterruptedException {
private static String get(HttpClient client, int port, String pathAndQuery) throws IOException, InterruptedException {
private static String post(HttpClient client, int port, String path, String json) throws IOException, InterruptedException {
private static URI uri(int port, String pathAndQuery) {
public ErrorResponse {
private ExerciseRunner() {
public static void main(String[] args) {
public GreetingService greetingService(GreetingProperties properties) {
public GreetingProperties {
private static String defaultText(String candidate, String fallback) {
public GreetingRequest {
public GreetingResponse {
public GreetingService(GreetingProperties properties) {
public GreetingResponse hello(String rawName, String rawPunctuation) {
public String normalizeName(String rawName) {
public String normalizePunctuation(String rawPunctuation) {
public HelloController(GreetingService greetings) {
public Map<String, String> index() {
public GreetingResponse helloJson(@RequestBody GreetingRequest request) {
public Map<String, String> health() {
public static void main(String[] args) {
public void buildsDefaultGreetingWhenNameIsMissing() {
public void trimsAndCollapsesNameWhitespace() {
public void acceptsQuestionMarkPunctuation() {
public void rejectsUnsupportedPunctuation() {
public void rejectsNamesLongerThanConfigurationAllows() {
public static void startServer() {
public static void stopServer() {
public void getHelloEndpointReturnsJsonGreeting() throws IOException, InterruptedException {
public void postHelloEndpointUsesSameServiceRules() throws IOException, InterruptedException {
public void badPunctuationReturnsHttpBadRequest() throws IOException, InterruptedException {
public void simpleHealthEndpointReturnsUp() throws IOException, InterruptedException {
private HttpResponse<String> get(String pathAndQuery) throws IOException, InterruptedException {
private HttpResponse<String> post(String path, String json) throws IOException, InterruptedException {
private URI uri(String pathAndQuery) {
public GreetingService(GreetingProperties properties) {
public GreetingResponse hello(String rawName, String rawPunctuation) {
public String normalizeName(String rawName) {
public String normalizePunctuation(String rawPunctuation) {