Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

JSON, records and the request body

A REST API usually exchanges data as JSON. The quarkus-rest-jackson extension turns Java objects into JSON and back through the Jackson library. You do not write the conversion by hand: a method returns an object, and the client receives JSON. The handiest way to describe data is records from Java 16+: short immutable classes whose fields Jackson can see.

The post model

// src/main/java/com/example/blog/Post.java
package com.example.blog;
 
public record Post(long id, String title, String body) {
}

The Post record describes a blog post: its number, title and text.

A list of posts as JSON

Until a database is connected, we keep posts in memory, in a field of the resource. The resource is created once, so the data lives while the application runs. Rewrite PostResource like this:

// src/main/java/com/example/blog/PostResource.java
package com.example.blog;
 
import jakarta.ws.rs.GET;
import jakarta.ws.rs.NotFoundException;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
 
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
 
@Path("/posts")
public class PostResource {
 
    private final Map<Long, Post> posts = new ConcurrentHashMap<>();
    private final AtomicLong nextId = new AtomicLong(1);
 
    public PostResource() {
        long id = nextId.getAndIncrement();
        posts.put(id, new Post(id, "Hello, Quarkus", "The first post of the blog."));
    }
 
    @GET
    public List<Post> list() {
        return List.copyOf(posts.values());
    }
 
    @GET
    @Path("/{id}")
    public Post get(@PathParam("id") long id) {
        Post post = posts.get(id);
        if (post == null) {
            throw new NotFoundException();
        }
        return post;
    }
}

ConcurrentHashMap and AtomicLong are safe when requests arrive at the same time. There is no @Produces: for objects Quarkus returns JSON by itself. The NotFoundException exception becomes a 404 response.

curl http://localhost:8080/posts
curl http://localhost:8080/posts/1
[{"id":1,"title":"Hello, Quarkus","body":"The first post of the blog."}]
{"id":1,"title":"Hello, Quarkus","body":"The first post of the blog."}

Accepting JSON in a POST request

For the data the client sends we create a separate record — without an id, because the server assigns it:

// src/main/java/com/example/blog/NewPost.java
package com.example.blog;
 
public record NewPost(String title, String body) {
}

A method parameter without an annotation is the request body. Quarkus reads the JSON and creates a NewPost from it:

// src/main/java/com/example/blog/PostResource.java
@POST
public Response create(NewPost newPost) {
    long id = nextId.getAndIncrement();
    Post post = new Post(id, newPost.title(), newPost.body());
    posts.put(id, post);
    return Response.created(URI.create("/posts/" + id))
            .entity(post)
            .build();
}

Add the imports jakarta.ws.rs.POST, jakarta.ws.rs.core.Response and java.net.URI. The Response class lets you set the status and headers: created(...) gives the 201 Created status and a Location header with the address of the new post, and entity(...) sets the response body.

curl -i -X POST http://localhost:8080/posts \
     -H "Content-Type: application/json" \
     -d '{"title":"Second post","body":"Text"}'
HTTP/1.1 201 Created
Content-Type: application/json;charset=UTF-8
Location: http://localhost:8080/posts/2

{"id":2,"title":"Second post","body":"Text"}

Deleting and a response without a body

// src/main/java/com/example/blog/PostResource.java
@DELETE
@Path("/{id}")
public Response delete(@PathParam("id") long id) {
    if (posts.remove(id) == null) {
        throw new NotFoundException();
    }
    return Response.noContent().build();
}

curl -i -X DELETE http://localhost:8080/posts/2 returns HTTP/1.1 204 No Content — a successful answer without a body. The import is jakarta.ws.rs.DELETE.

Common mistake

If you send JSON without the Content-Type header, the server does not accept it:

curl -i -X POST http://localhost:8080/posts -d '{"title":"x","body":"y"}'
HTTP/1.1 415 Unsupported Media Type
content-length: 0

With the -d flag curl sends application/x-www-form-urlencoded by default, while the method expects JSON. Add -H "Content-Type: application/json". If the JSON itself is broken, for example a brace is missing, the answer is 400 Bad Request.

Thanks for reading!

Donate to the site — we work hard for you.