Implementing CREATE operation

In this step, you'll write a service method for creating a blog post. By completing this step, you'll learn to map your service with the HTTP POST (@Post) method and make your own request converter (@RequestConverter).

What you need

You must have the following files ready for creating a blog post. You can always download the full version, instead of creating one yourself.

1. Map HTTP method

Let's start mapping the HTTP POST method with our service method:

  1. Declare a service method, createBlogPost(), in the class BlogService.
  2. Map this service method with the HTTP POST method by adding the @Post annotation.
  3. Bind the endpoint /blogs to the method.
import com.linecorp.armeria.server.annotation.Post;

public final class BlogService {

  public void createBlogPost(BlogPost blogPost) {}

2. Handle parameters

Let's receive blog post information through a request body. Armeria's request converter converts request parameters in HTTP messages into Java objects for you. In the request converter, we define what keys of a JSON object to map with what properties of a Java object.

Let's first write a request converter and then register the request converter to the service method.

Write a request converter

Armeria's request converter converts a request body from a client into a Java object for you.

We can use Armeria's default JacksonRequestConverterFunction as is, but here let's give a go at customizing a request converter for our blog post requests. We want to convert blog post details into a Java object.

  1. Create a file and declare a class, implementing the RequestConverterFunction interface. For the sake of simplicity, generate impromptu IDs for this tutorial.
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.linecorp.armeria.server.annotation.RequestConverterFunction;
    import java.util.concurrent.atomic.AtomicInteger;
    final class BlogPostRequestConverter implements RequestConverterFunction {
      private static final ObjectMapper mapper = new ObjectMapper();
      private AtomicInteger idGenerator = new AtomicInteger(); // Blog post ID
  2. Add a method retrieving a value of a given key in a JSON object:
    import com.fasterxml.jackson.databind.JsonNode;
    final class BlogPostRequestConverter implements RequestConverterFunction {
      static String stringValue(JsonNode jsonNode, String field) {
        JsonNode value = jsonNode.get(field);
        if (value == null) {
          throw new IllegalArgumentException(field + " is missing!");
        return value.textValue();
  3. Customize the default convertRequest() method as follows.
    import com.linecorp.armeria.server.ServiceRequestContext;
    import com.linecorp.armeria.common.AggregatedHttpRequest;
    import com.linecorp.armeria.common.annotation.Nullable;
    import java.lang.reflect.ParameterizedType;
    final class BlogPostRequestConverter implements RequestConverterFunction {
      public Object convertRequest(ServiceRequestContext ctx,
        AggregatedHttpRequest request, Class<?> expectedResultType,
        @Nullable ParameterizedType expectedParameterizedResultType)
          throws Exception {
        if (expectedResultType == BlogPost.class) {
          JsonNode jsonNode = mapper.readTree(request.contentUtf8());
          int id = idGenerator.getAndIncrement();
          String title = stringValue(jsonNode, "title");
          String content = stringValue(jsonNode, "content");
          return new BlogPost(id, title, content); // Create an instance of BlogPost object
        return RequestConverterFunction.fallthrough();

Register a request converter

In this step, assign the request converter we customized to our service method. Annotate the service method with @RequestConverter and specify the RequestConverterFunction class as BlogPostRequestConverter.class.
import com.linecorp.armeria.server.annotation.RequestConverter;

public final class BlogService {

  public void createBlogPost(BlogPost blogPost) {
    // Implement blog service

3. Implement service code

When the request for creation is received, our request converter creates an instance of a blog post object for us. We want to save the blog post object in the map (blogPosts) created in the BlogService class.

Let's store the blog post information in the map by adding line 4, in the createBlogPost() method.
3public void createBlogPost(BlogPost blogPost) {
4  blogPosts.put(blogPost.getId(), blogPost);

4. Return response

Now, it's time to return a response to our client. As the response, return the information received, with additional information including the ID of the post, created time, plus the modified time which would be identical to the created time.

Let's return a response for blog post creation:

  1. Replace the return type of the createBlogPost() method from void to HttpResponse.

  2. Create and return an HTTP response using Armeria's HttpResponse with the information of the post created.
    import com.linecorp.armeria.common.HttpResponse;
    public final class BlogService {
      public HttpResponse createBlogPost(BlogPost blogPost) {
        return HttpResponse.ofJson(blogPost);

5. Test creating a blog post

  1. Run the server like we did in Step 1. Create a server by running the main() method or using Gradle. When you see the message, "Server has been started", you can try testing the service method.

  2. Call the service method for creating a blog post. Here, we'll use cURL.

    $ curl --request POST 'localhost:8080/blogs' \
    -H 'Content-Type: application/json' \
    -d '{"title":"My first blog", "content":"Hello Armeria!"}'
  3. Check the return value. The response includes created and modified times.

    {"id":0,"title":"My first blog","content":"Hello Armeria!","createdAt":...,"modifiedAt":...}

You can test this also with Armeria's Documentation service. See Using DocService after adding service methods for instructions.

Next step

In this step, we've written a method to implement a CREATE operation and used Armeria's annotations; @Post and @RequestConverter.

Next, at Step 5. Implement READ, we'll implement a READ operation to read a single post and also multiple posts.