Capitolo 1 · Sviluppo web con Spring Boot: il corso pratico
Hai appena avviato il tuo primo progetto Spring Boot: il server è partito, i log scorrono con aria di successo, apri il browser su localhost:8080 e ti accoglie un bel 404.
Il primo istinto è pensare di aver rotto qualcosa. Il secondo è incollare l'errore su Google e sperare nella risposta giusta al primo colpo.
La realtà? Non hai rotto niente. Quel 404 è la prima conversazione vera tra il tuo programma e il resto del mondo, e capire perché arriva è il modo più veloce per capire come funziona un controller.
Per rendere tutto più concreto, in questa serie costruiremo il gestionale del pronto soccorso di un ospedale (di fantasia, pazienti compresi). Nel gergo del software, il problema reale che un programma risolve si chiama dominio: qui sono i pazienti che arrivano, i medici, le regole del pronto soccorso. Spring Boot, il JSON e la porta 8080 non fanno parte del dominio: sono l'infrastruttura, l'impianto che fa arrivare le richieste. Nell'ultimo pezzo della serie le terremo ben separate. Oggi apriamo il primo sportello. Mettiamo da parte le definizioni da manuale e seguiamo una richiesta dal momento in cui premi Invio a quello in cui torna la risposta.
Cosa ti ritrovi nel progetto appena creato?
Immagina di aver generato il progetto da Spring Initializr (o dal wizard del tuo IDE, che in realtà chiama lo stesso servizio). Apri la cartella e ti trovi davanti una selva di file e sottocartelle. Quanti te ne servono davvero per cominciare? Tre.
- La classe launcher. È quella con il metodo
maine l'annotazione@SpringBootApplication: il punto d'ingresso, quello da cui parte tutto. Per un bel po' non la toccherai, perché Spring Boot si occupa da solo della configurazione (si chiama autoconfigurazione). - Il file
pom.xml. È la carta d'identità del progetto: gruppo, nome, versione di Java e, soprattutto, le dipendenze. Se hai scelto Spring Web c'è lo starter web, che si porta dietro Spring MVC e il server Tomcat. - Il file
application.properties. Sta insrc/main/resourcesed è il pannello di controllo: la porta, l'indirizzo su cui il server ascolta, il nome dell'applicazione. All'inizio contiene una riga sola.
Ecco come appaiono, uno per uno, in un progetto appena generato (il nome del pacchetto cambia con il tuo). Prima di tutto il launcher:
package com.ospedale.prontosoccorso;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ProntosoccorsoApplication {
public static void main(String[] args) {
SpringApplication.run(ProntosoccorsoApplication.class, args);
}
}Poi la dipendenza che hai scelto nel wizard, dentro il pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc</artifactId>
</dependency>Una nota sul nome: con Spring Boot 4 questo starter si chiama spring-boot-starter-webmvc, nelle versioni precedenti era spring-boot-starter-web. Cambia il nome, il lavoro è lo stesso. E infine la configurazione, con la sua unica riga:
spring.application.name=prontosoccorso
Se un giorno la porta 8080 ti servirà per altro, basta aggiungere qui una riga come server.port=9090.
Com'era prima di Spring Boot?
Un tempo, nel mondo Java, per pubblicare anche un solo indirizzo web si scriveva a mano una servlet, si compilava un file di configurazione XML (web.xml) e si installava a parte un application server come Tomcat o JBoss, configurandolo per conto suo. Spring Boot ha tolto di mezzo gran parte di questa burocrazia: il server web è già dentro l'applicazione e la configurazione è quasi tutta automatica. Per questo, nel progetto appena generato, non c'è traccia né di web.xml né di un server da installare.
Prima di tutto: chi parla con chi?
Quando scrivi localhost:8080/pazienti/status nella barra degli indirizzi, il tuo browser fa il client: è quello che chiede. Dall'altra parte c'è il server, che nel nostro caso è Tomcat: il web server che Spring Boot accende per te sulla porta 8080. È quella di default, si può cambiare, ma finché non hai un motivo lascia stare. Nel log di avvio lo vedi nero su bianco:
Tomcat initialized with port 8080 (http) Starting Servlet engine: [Apache Tomcat/11.0.24] Tomcat started on port 8080 (http) with context path '/' Started ProntosoccorsoApplication in 1.283 seconds
Per capirsi usano un protocollo comune, l'HTTP, che si regge su una regola semplice: a ogni richiesta (request) del client corrisponde una risposta (response) del server. E se qualcosa va storto, la risposta te lo dice. È esattamente quello che è successo con il tuo 404.
Un dettaglio da fissare subito: il client non è per forza un browser. Può essere l'app sul telefono del medico, un altro server, uno script che gira di notte. A Tomcat non importa chi bussa, importa solo che la richiesta sia fatta bene.
L'indirizzo è un percorso: controller più metodo
Nel progetto c'è una classe, chiamiamola PazienteController, con tre annotazioni che fanno tutto il lavoro. Prima di guardarle, una scomposizione dell'indirizzo che ti scrivi nel browser:
Ed ecco le annotazioni:
| Annotazione | Dove sta | Cosa dice a Spring |
|---|---|---|
@RestController | sulla classe | "Rispondi alle richieste web restituendo dati, non pagine HTML." |
@RequestMapping("pazienti") | sulla classe | "Tutti i miei indirizzi cominciano con /pazienti." |
@GetMapping("status") | sul metodo | "Quando arriva una GET su /pazienti/status, esegui questo metodo." |
Il percorso completo è il prefisso della classe più il pezzo del metodo: /pazienti + /status. Il metodo non ha niente di esotico: restituisce una stringa, per esempio "Pronto soccorso operativo", ed è proprio quella che compare nel browser. Messo tutto insieme, il controller è questo, ed è tutto il codice che serve:
package com.ospedale.prontosoccorso.controllers;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("pazienti")
public class PazienteController {
@GetMapping("status")
public String controllaStato() {
return "Pronto soccorso operativo";
}
}Riavvii l'applicazione, apri localhost:8080/pazienti/status e il browser ti mostra la stringa restituita dal metodo:

Una parola in più su @RestController, che è la più difficile da spiegare. È un controller "speciale": manda ciò che restituisci attraverso il traduttore di Spring (quello che più avanti trasforma gli oggetti in JSON) invece di cercare una pagina HTML da mostrare. Per ora basta sapere che è lei a fare la differenza.
Cosa succede, in ordine, quando premi Invio?
- Il browser compone una richiesta GET per
localhost:8080/pazienti/statuse la spedisce. - Tomcat la riceve sulla porta 8080 e la consegna a Spring.
- Spring, con un componente chiamato
DispatcherServlet, guarda gli indirizzi dichiarati nei controller e cerca quello che corrisponde. - Il metodo
controllaStato(), collegato a/pazienti/status, viene eseguito e restituisce la sua stringa. - Il server rimanda indietro la risposta: il testo, più un numero che dice com'è andata (qui, 200).
Ora il mistero del 404 si spiega da solo.
Perché / dà 404 e /pazienti/status no?
Sul percorso / non è mappato niente: nessuna classe, nessun metodo. Al terzo passo Spring non trova nessuno a cui passare la richiesta, e il server risponde con l'unica cosa onesta che può dire: non trovo nulla a questo indirizzo. Stessa storia con localhost:8080/pazienti/blabla: il controller esiste, ma quel metodo no.
Quel 404 non è un guasto del server, è la risposta giusta a una richiesta che non porta da nessuna parte. Nessuno ha ancora aperto uno sportello a quell'indirizzo. Questo è ciò che vedi nel browser aprendo localhost:8080 in un progetto senza nessun controller:

Un controller non basta mai
In un ospedale gli sportelli sono tanti, e il prefisso serve proprio a smistare le richieste:
/pazientiva aPazienteController/mediciva aMedicoController/repartiva aRepartoController
Il nome finisce sempre in Controller (una convenzione che conviene rispettare da subito) e ognuno si occupa di un solo ambito: uno per i pazienti, uno per i medici, uno per i reparti. Se mischi tutto nella stessa classe, tra un mese non ci troverai più niente.
Gli status code: il numero che il browser sa leggere
Ogni risposta si porta dietro un numero, lo status code, che dice al client com'è andata senza che debba leggere il testo. Lo capisce un browser, ma lo capisce anche un'altra macchina, e per un'API è tutto quello che serve. Le famiglie sono cinque:
- 1xx, informazioni: un messaggio provvisorio, del tipo "ricevuto, vai avanti".
- 2xx, successo: la richiesta è andata a buon fine. Il classico è 200.
- 3xx, redirezione: "qui non c'è più, prova a quest'altro indirizzo".
- 4xx, errore del client: hai sbagliato tu. Il nostro 404 sta qui.
- 5xx, errore del server: si è rotto qualcosa lì dentro, di solito è saltata un'eccezione. Il classico è 500.
Il tuo 404 è un 4xx, quindi colpa di chi chiede: ha bussato a una porta che non c'è. Un 500, al contrario, è colpa di chi sta dietro il server, cioè di chi ha scritto il codice. Cioè tua, tra poco.
Se ti servono i codici più usati con qualche esempio concreto, li ho messi in fila in REST API spiegate semplici.
Conclusione
Un endpoint, in pratica, sono tre annotazioni e un metodo che restituisce una stringa. Tutto il resto, cioè client e server, richieste e risposte, status code, serve a capire cosa succede tra il browser e quel metodo.
Lo sportello ora risponde. Nel prossimo pezzo tocca alla domanda che prima o poi arriva a tutti: come faccio a mandare al server i dati di un paziente?

Dario Amato
Sviluppatore software
Informatico con 15 anni di esperienza nello sviluppo software, guidato da una forte curiosità per le nuove frontiere dell'AI e del Web Marketing.
