REST Webservices in Java klingen am Anfang gerne etwas abstrakt: HTTP, Ressourcen, JSON, Statuscodes, JAX-RS - ziemlich viele Begriffe auf einmal. In der Praxis ist das aber weniger Magie, als es aussieht. Wenn du Java schon ein bisschen kennst, kannst du mit ein paar Grundbausteinen sehr schnell deinen ersten eigenen REST-Webservice aufsetzen.
In diesem Artikel gehen wir das Schritt für Schritt durch: Was REST eigentlich ist, wie das mit HTTP zusammenhängt und wie du mit JavaEE bzw. JakartaEE und einem Application Server wie WildFly einen einfachen REST-Service baust, den du z.B. mit dem Browser oder mit curl testen kannst.
Was ist REST - in der Praxis?
REST ist kein Framework, sondern ein Architekturstil. Die Theorie dahinter kann man sehr wissenschaftlich aufziehen, aber für den Alltag reicht erst mal ein einfaches Bild: Ein REST-Webservice stellt Ressourcen über URLs bereit und benutzt die HTTP-Methoden (GET, POST, PUT, DELETE usw.), um mit diesen Ressourcen zu arbeiten.
Du kannst dir eine Ressource wie einen fachlichen Gegenstand vorstellen: Nutzer, Bestellung, Todo-Eintrag, Produkt. Jeder Ressourcentyp hängt an einer URL, zum Beispiel /api/todos für eine Liste von Todos oder /api/todos/42 für ein bestimmtes Todo mit der ID 42.
Dann kommen die HTTP-Methoden ins Spiel:
Mit GET holst du Daten. Mit POST legst du etwas Neues an. Mit PUT oder PATCH aktualisierst du etwas. Mit DELETE löschst du etwas. Mehr ist es am Anfang nicht. Wenn du das verstanden hast, ist die halbe Miete für REST schon drin.
HTTP kurz auf dem Schirm
REST baut auf HTTP auf. HTTP bringt ein paar Dinge mit, die du für Webservices brauchst: Methoden (GET, POST usw.), Statuscodes (200, 404, 500 …) und Header für Zusatzinformationen (z.B. Content-Type).
Wichtige Statuscodes für den Anfang:
200 OK - alles gut, die Anfrage hat geklappt. 201 Created - etwas wurde neu angelegt (typisch bei POST). 400 Bad Request - die Anfrage war fehlerhaft (z.B. ungültiges JSON). 404 Not Found - die Ressource wurde nicht gefunden. 500 Internal Server Error - auf dem Server ist etwas schiefgelaufen.
Es lohnt sich, diese Codes als „Sprache“ zwischen Client und Server zu sehen. Dein Java-Code muss nicht nur Daten liefern, sondern auch passende Statuscodes zurückgeben, damit Clients wissen, was passiert ist.
JSON als Datentauscher
Früher wurden Webservices gerne mit XML umgesetzt. Heute ist JSON fast überall Standard, vor allem bei REST. JSON ist leicht zu lesen, einfach zu parsen und passt gut zu modernen Frontends und Tools wie Postman oder curl.
In Java wird ein Objekt einfach in JSON serialisiert. Du hast zum Beispiel eine Klasse Todo mit Feldern wie id, title und done. Dein REST-Framework kümmert sich darum, ein Todo-Objekt automatisch in JSON umzuwandeln und umgekehrt aus JSON wieder ein Java-Objekt zu bauen.
REST in Java: JAX-RS in JavaEE / JakartaEE
In der Java-Welt gibt es einen Standard für REST-Webservices: JAX-RS. In modernen Servern wie WildFly ist JAX-RS schon eingebaut. Du musst also kein extra Framework ziehen, wenn du im JavaEE-/JakartaEE-Umfeld unterwegs bist.
Der Kern von JAX-RS sind Klassen, die per Annotation als Ressource markiert werden. Eine Ressource ist einfach eine ganz normale Java-Klasse, die bestimmte Methoden für HTTP-Operationen anbietet.
Ein minimaler REST-Endpunkt könnte so aussehen:
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
@Path("/hello")
public class HelloResource {
@GET
@Produces(MediaType.TEXT_PLAIN)
public String hello() {
return "Hallo REST";
}
}
Was passiert hier?
Die Klasse HelloResource hängt an der URL /hello. Die Methode hello() reagiert auf HTTP-GET-Anfragen. Mit @Produces sagst du, dass reiner Text zurückgegeben wird. Wenn deine Anwendung unter /api läuft, bekommst du unter /api/hello die Antwort „Hallo REST“ im Browser.
Der Einstiegspunkt: Application-Klasse
Damit dein Server weiß, dass es überhaupt JAX-RS-Ressourcen gibt, legst du eine Klasse an, die von jakarta.ws.rs.core.Application erbt und mit @ApplicationPath versehen wird. Darüber definierst du das Basis-URL-Präfix für alle REST-Endpoints.
import jakarta.ws.rs.ApplicationPath;
import jakarta.ws.rs.core.Application;
@ApplicationPath("/api")
public class RestApplication extends Application {
// Keine weitere Logik noetig
}
Ab jetzt laufen alle Ressourcen unterhalb von /api. In Kombination mit der HelloResource »lebt« dein Endpunkt also unter /api/hello.
Ein kleines, realistisches Beispiel: Todo-Service
Jetzt bauen wir ein etwas greifbareres Beispiel: einen mini Todo-Service. Ziel: Mit GET alle Todos holen, mit POST ein neues Todo anlegen.
Als Erstes definierst du eine einfache Datenklasse:
public class Todo {
private Long id;
private String title;
private boolean done;
public Todo() {
}
public Todo(Long id, String title, boolean done) {
this.id = id;
this.title = title;
this.done = done;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public boolean isDone() {
return done;
}
public void setDone(boolean done) {
this.done = done;
}
}
Kein Zauber: Das ist ein klassisches POJO mit einem Default-Konstruktor, damit der JSON-Mapper es instanziieren kann.
Als Nächstes kommt die Ressource, die diese Todos verwaltet. Für den Anfang reicht eine einfache In-Memory-Liste:
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
@Path("/todos")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class TodoResource {
// Nur Demo: in echt wuerde man hier eine Datenbank verwenden
private static final List<Todo> TODOS = new ArrayList<>();
private static long sequence = 1L;
@GET
public List<Todo> findAll() {
return TODOS;
}
@POST
public Response create(Todo todo) {
todo.setId(sequence++);
TODOS.add(todo);
URI location = URI.create("/api/todos/" + todo.getId());
return Response .created(location) .entity(todo) .build();
}
}
Hier steckt schon einiges drin:
Die Klasse hängt an /todos. Mit @Produces und @Consumes legst du fest, dass Ein- und Ausgabe als JSON laufen. findAll() reagiert auf GET /todos und liefert die komplette Liste zurück. create() reagiert auf POST /todos, hängt eine neue ID an, legt das Todo in der Liste ab und antwortet mit HTTP-Status 201 (Created) plus Location-Header, wo das neue Todo zu finden ist.
Die Response baust du über das Response-Objekt. Response.created(location) setzt automatisch den Statuscode 201 und den Location-Header. Mit .entity(todo) lieferst du gleichzeitig das angelegte Objekt im JSON-Body zurück.
Den Service testen: Browser, curl, Postman
Angenommen, deine Anwendung läuft auf WildFly unter http://localhost:8080/myapp. Dann liegen deine REST-Endpoints z.B. unter:
http://localhost:8080/myapp/api/todos
Ein GET kannst du einfach im Browser testen: Wenn es noch keine Todos gibt, bekommst du wahrscheinlich ein leeres Array [] zurück.
Ein POST geht gut mit curl oder Postman. Mit curl könnte das so aussehen:
curl -X POST \ -H "Content-Type: application/json" \ -d '{"title": "Erstes Todo", "done": false}' \ http://localhost:8080/myapp/api/todos
Wenn alles sauber konfiguriert ist, bekommst du ein JSON mit gesetzter ID zurück, zum Beispiel:
{
"id": 1,
"title": "Erstes Todo",
"done": false
}
Ein erneuter GET auf /api/todos liefert dir dann eine Liste mit diesem Eintrag. Du hast damit offiziell deinen ersten REST-Webservice in Java gebaut.
Wie passt Maven da rein?
Wenn du mit Maven arbeitest, liegt in einem JavaEE-/JakartaEE-Projekt oft schon alles Wichtige im Server. WildFly bringt zum Beispiel JAX-RS mit. Wichtig ist nur, dass du dein Projekt als WAR baust und korrekt deployst.
In einer typischen pom.xml sieht das grob so aus:
<project>
<modelVersion>4.0.0</modelVersion>
<packaging>war</packaging>
<dependencies>
<dependency>
<groupId>jakarta.platform</groupId>
<artifactId>jakarta.jakartaee-web-api</artifactId>
<version>10.0.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
Das scope „provided“ bedeutet: Der Application Server stellt die Bibliotheken zur Verfügung, du brauchst sie zur Kompilierung, packst sie aber nicht mit ins WAR.
Typische Stolperfallen am Anfang
Gerade in den ersten Monaten stolperst du bei REST in Java oft über die gleichen Punkte. Es hilft, die im Hinterkopf zu haben.
Wenn dein Endpunkt gar nicht aufrufbar ist (404), ist oft der Pfad die Ursache: Stimmt @ApplicationPath? Passt der Pfad in @Path? Rufst du die URL wirklich mit dem richtigen Kontextpfad der Anwendung auf?
Wenn du bei POST 415 (Unsupported Media Type) bekommst, fehlt meistens der passende Content-Type-Header (application/json) oder @Consumes(MediaType.APPLICATION_JSON) ist nicht gesetzt oder falsch.
Bei 400-Fehlern liegt häufig ein JSON-Parsing-Problem vor. Dann lohnt es sich, das JSON genau anzuschauen: Stimmen die Feldnamen mit den Java-Feldern überein? Gibt es ein leeres JSON, wo ein Objekt erwartet wird? Hat deine Klasse einen no-arg-Konstruktor?
Wohin geht die Reise nach dem ersten REST-Service?
Mit den Bausteinen aus diesem Artikel kannst du schon eine Menge umsetzen: kleine CRUD-Services, simple Backend-Schnittstellen für ein Frontend oder ein internes Tool. Der nächste Schritt ist meist, die In-Memory-Liste durch eine Datenbank zu ersetzen und JPA dazuzunehmen.
Ebenso wichtig wird irgendwann das Thema Fehlerbehandlung und saubere Statuscodes. Statt in jeder Ressource Exceptions roh nach außen zu werfen, kannst du globale Exception-Mapper einsetzen und so ein konsistentes Fehlerformat für alle Endpoints definieren.
Auch Versionierung (z.B. /api/v1/...), Security (z.B. JWT, Rollen) und Dokumentation (OpenAPI/Swagger) kommen mit der Zeit dazu. Der Punkt ist: Die Basis bleibt die gleiche. Resources, Pfade, HTTP-Methoden, JSON - das ändert sich nicht.
Wenn du dich an das Zusammenspiel von JAX-RS, HTTP und JSON gewöhnt hast, fällt es dir auch leichter, später auf andere Stacks zu schauen, zum Beispiel Spring Boot. Das Konzept von REST bleibt überall ähnlich, nur die Annotationen und Frameworks sehen etwas anders aus.
Fazit
REST Webservices in Java sind kein Hexenwerk. Du brauchst ein Grundverständnis von HTTP, ein paar JAX-RS-Annotationen und einen Application Server, der dir den Standard bereitstellt. Dann kannst du sehr schnell produktive Schnittstellen aufbauen, die von Frontends, anderen Systemen oder einfachen Skripten genutzt werden.
Wenn du heute mit einer minimalen Ressource startest und sie Schritt für Schritt ausbaust, lernst du genau die Dinge, die du im Alltag wirklich brauchst: sinnvolle Pfade, passende Statuscodes, saubere JSON-Modelle und verständliche Schnittstellen. Der Rest kommt mit Erfahrung fast automatisch.
Der nächste sinnvolle Schritt: Bau dir einen kleinen eigenen Service, der irgendetwas Nützliches für dich tut - egal ob das eine Todo-Liste ist, ein kleines Notiz-Backend oder ein Service, der dir einfache Kennzahlen aus einer Datenbank ausliest. Hauptsache, du kommst ins Machen.
Und wenn dein erster REST-Endpunkt sauber läuft, hast du schon einen ziemlich großen Brocken moderner Backend-Entwicklung verstanden.
