Spring Annotations

While you are encouraged to use JAX-RS annotation for defining REST endpoints, Quarkus provides a compatibility layer for Spring Web in the form of the spring-web extension.  

This step shows how your Quarkus application can leverage the well-known Spring Web annotations to define RESTful services.

Creating Controllers

Create a new file at:  

src/main/java/org/acme/quickstart/FruitController.java
Enter the following implementation:
package org.acme.quickstart;

import java.util.List;
import java.util.Optional;

import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;

@RestController
@RequestMapping("/fruits")
public class FruitController {

    private final FruitRepository fruitRepository;

    public FruitController(FruitRepository fruitRepository) {
        this.fruitRepository = fruitRepository;
    }

    @GetMapping(produces = "application/json")
    public Iterable<Fruit> findAll() {
        return fruitRepository.findAll();
    }


    @DeleteMapping("/{id}")
    public void delete(@PathVariable(name = "id") long id) {
        fruitRepository.deleteById(id);
    }

    @PostMapping(path = "/name/{name}/color/{color}", produces = "application/json")
    public Fruit create(@PathVariable(name = "name") String name, @PathVariable(name = "color") String color) {
        return fruitRepository.save(new Fruit(name, color));
    }

    @PutMapping(path = "/id/{id}/color/{color}", produces = "application/json")
    public Fruit changeColor(@PathVariable(name = "id") Long id, @PathVariable(name = "color") String color) {
        Optional<Fruit> optional = fruitRepository.findById(id);
        if (optional.isPresent()) {
            Fruit fruit = optional.get();
            fruit.setColor(color);
            return fruitRepository.save(fruit);
        }

        throw new IllegalArgumentException("No Fruit with id " + id + " exists");
    }

    @GetMapping(path = "/color/{color}", produces = "application/json")
    public List<Fruit> findByColor(@PathVariable(name = "color") String color) {
        return fruitRepository.findByColor(color);
    }
}

Notice the use of familiar Spring annotations like @GetMapping and @PathVariable. This exposes a set of RESTful APIs: 

  • GET /fruits - Retrieve all Fruits as a JSON array
  • DELETE /fruits/{id} - Delete by ID
  • POST /fruits/name/{name}/color/{color} - create a new Fruit with a name and color
  • PUT /fruits/id/{id}/color/{color} - Update a fruit with a new color
  • GET /fruits/color/{color} - Retrieve all fruits of the specified color

Testing the Application

The application should still be running from the first step. You don't need to restart the process; Quarkus has been incorporating all of the changes as they were made. If you stopped the process, you can restart it by running:  

mvn compile quarkus:dev
With this in place, you can now test your fruits API.
Note: If you have the command line JSON parser jq installed, all of the following curl commands can be piped into jq for more readable output.

Retrieve a list of all fruits:  

curl -s https://localhost:8080/fruits

 The output, when piped through jq, will be:  

[
  {
    "id": 1,
    "name": "cherry",
    "color": "red"
  },
  {
    "id": 2,
    "name": "orange",
    "color": "orange"
  },
  {
    "id": 3,
    "name": "banana",
    "color": "yellow"
  },
  {
    "id": 4,
    "name": "avocado",
    "color": "green"
  },
  {
    "id": 5,
    "name": "strawberry",
    "color": "red"
  }
]

Add a new fruit:  

curl -X POST -s http://localhost:8080/fruits/name/apple/color/red
Change the color of the newly added apple to green:
curl -X PUT -s http://localhost:8080/fruits/id/6/color/green

Retrieve all green fruits:  

curl -s http://localhost:8080/fruits/color/green

Exercising Beans using Spring DI Annotations

As a final test, you'll create another bean to access the injected beans and configuration using Spring DI annotations.  

Create a new file at:
src/main/java/org/acme/quickstart/TasterController.java

Edit the file and add the following class:  

package org.acme.quickstart;

import java.util.ArrayList;
import java.util.List;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import org.springframework.web.bind.annotation.PathVariable;

@RestController
@RequestMapping("/taster")
public class TasterController {

    private final FruitRepository fruitRepository;

    private final TasterBean tasterBean;

    public TasterController(FruitRepository fruitRepository, TasterBean tasterBean) {
        this.fruitRepository = fruitRepository;
        this.tasterBean = tasterBean;
    }

    @GetMapping(produces = "application/json")
    public List<TasteResult> tasteAll() {
        List<TasteResult> result = new ArrayList<>();

        fruitRepository.findAll().forEach(fruit -> {
            result.add(new TasteResult(fruit, tasterBean.taste(fruit.getName())));
        });
        return result;
    }

    @GetMapping(path = "/{color}", produces = "application/json")
    public List<TasteResult> tasteByColor(@PathVariable(name = "color") String color) {
        List<TasteResult> result = new ArrayList<>();
        fruitRepository.findByColor(color).forEach(fruit -> {
            result.add(new TasteResult(fruit, tasterBean.taste(fruit.getName())));
        });
        return result;
    }

    public class TasteResult {
        public Fruit fruit;
        public String result;

        public TasteResult(Fruit fruit, String result) {
            this.fruit = fruit;
            this.result = result;
        }

    }
}

This implementation is using Spring Rest annotations like @GetMapping, but also injecting repository and taster bean in the constructor. This controller exposes 2 RESTful APIs:  

  • GET /taster - taste all fruits and report result
  • GET /taster/{color} - Taste only fruits of the specified color
Again, since the Quarkus development mode does not require a restart when new code is added, you can simply test the method once the code is saved:
When piped through jq, the output will be:
[
  {
    "fruit": {
      "id": 1,
      "name": "cherry",
      "color": "red"
    },
    "result": "CHERRY: TASTES GREAT !"
  },
  {
    "fruit": {
      "id": 2,
      "name": "orange",
      "color": "orange"
    },
    "result": "ORANGE: TASTES GREAT !"
  },
  {
    "fruit": {
      "id": 3,
      "name": "banana",
      "color": "yellow"
    },
    "result": "BANANA: TASTES GREAT !"
  },
  {
    "fruit": {
      "id": 4,
      "name": "avocado",
      "color": "green"
    },
    "result": "AVOCADO: TASTES GREAT !"
  },
  {
    "fruit": {
      "id": 5,
      "name": "strawberry",
      "color": "red"
    },
    "result": "STRAWBERRY: TASTES GREAT !"
  }
]

Open the properties file at:  

And add a new suffix property. The value of this property, once present, will be appended to all tasting results.
taste.suffix = (if you like fruit!)

One way to test the new suffix is to retrieve all yellow fruits:

Notice that the capitializer StringFunction causes the suffix to be converted to upper case.

Cleaning Up

The coding portion of this course is finished, so stop the running process by entering CTRL-C in your terminal with the Maven command.

Daniel Oh
Daniel Oh
Senior Principal Technical Marketing Manager
Daniel Oh is a senior principal technical marketing manager at Red Hat to evangelize developers for building Cloud-Native Microservices and Serverless Functions with Cloud-Native Runtimes(i.e. Quarkus, Spring Boot, Node.js) and OpenShift/Kubernetes. Daniel also continues to contribute to various cloud open-source projects and ecosystems as a CNCF ambassador for accelerating DevOps adoption in enterprises. He's speaking at lots of technical seminars, workshops, and meetups to elaborate on new emerging technologies for enterprise developers & DevOps teams.