Swag now available

Spring Annotations

Introduction

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:

 

curl -s http://localhost:8080/taster

 

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 !"
  }
]

 

Adding a Suffix

Open the properties file at:  

src/main/resources/application.properties

 

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:

 

curl -s http://localhost:8080/taster/yellow

 

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.