Intro to SpringBoot3 with Kotlin: Part 2 —Controllers & Routing
The Series
- Part 1 — Spring Initializer
- Part 2 — Controllers & Routing (this article)
- Part 3 — Services & Business Logic
- Part 4 — Repositories & Data (JPA)
The Source Code
Introduction & Recap
In Part 1, we created the basic skeleton of our web server. In this section, we will create our first controller.
Controllers
A web controller is the part of your code that responds to a web request.
For example, if your server expects to get a request to mywebsite.com/greeting, you would configure a controller method (sometimes referred to as a handler) for that route, and you would return what a response to the user or browser making the web request.
Let’s do precisely that right now in order to see how SpringBoot supports this paradigm.
Greeting Controller
In your root package (the same one that contains your Application
class) create a GreetingController.kt
file.
Here, create a class and annotate it with @Controller
.
package com.example.springboot3-kotlin
import org.springframework.stereotype.Controller
@Controller
class GreetingController
This annotation tells SpringBoot to use this class for web requests. But in order to do so, SpringBoot needs to know what routes this controller should respond to.
We can set the routes for a controller at the class level, and/or at the method level. We will do both to see how that works.
Controller-wide Request Mapping
First, add a @RequestMapping(“/greeting”)
annotation to the class.
@Controller
@RequestMapping("/greeting")
class GreetingController
This tells spring that this controller class should respond to any web request that begins with “/greeting”
. We can create a method (aka handler) on the controller class to see this, and propagate the request mapping of the controller to the method by re-annotating it with @RequestMapping
.
Add a method, and annotate it with @RequestMapping
and @ResponseBody
.
@RequestMapping
@ResponseBody
fun rootGreeting(): String = "Hello World!"
We have seen @RequestMapping
before, but this time, it is inheriting the route from the parent controller class (ie, "/greeting"
). @ResponseBody
tells SpringBoot that whatever we return from this method, it should add it to the body of the response returned to the caller.
Your controller should now look like this:
package com.example.springboot3-kotlin
import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.ResponseBody
@Controller
@RequestMapping("/greeting")
class GreetingController {
@RequestMapping
@ResponseBody
fun rootGreeting(): String = "Hello World!"
}
Go ahead and run the Application again, and voila! you can go to http://localhost:8080/greeting, and you are presented with “Hello World!”.
Method-based Request Mappings
You might wonder why we would configure a RequestMapping on the controller and then add a blank one to the method. In practice, this most likely never happens.
What this pattern is intended for is to group routes with a common “parent” route together. We will do this now as well to see an example.
Create two new methods. One will respond in english, and one will respond in Spanish. Give them the appropriate routes of “/english”
and “/spanish”
, and return appropriate greetings.
Also, since all our handlers in the controller class are going to want to add their return value to the response body, we can move that annotation up to the class level as well.
You should end up with something like this.
package com.example.springboot3-kotlin
import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.ResponseBody
@Controller
@ResponseBody
@RequestMapping("/greeting")
class GreetingController {
@RequestMapping("/english")
fun englishGreeting(): String = "Hello World!"
@RequestMapping("/spanish")
fun spanishGreeting(): String = "¡Hola Mundo!"
}
Re-run the application and visit http://localhost:8080/english or http://localhost:8080/spanish to see the appropriate greetings!
Request Verbs
If you have any experience with web APIs, you probably noticed the lack of request verbs for these routes. For example, you can hit the routes as both GET and POST requests:
curl -X GET http://localhost:8080/greeting/spanish
¡Hola Mundo!
curl -X POST http://localhost:8080/greeting/spanish djpeach in /djpeach -> curl -X POST http://localhost:8080/greeting/spanish
¡Hola Mundo!
In most cases, you will want to control what methods your routes are available to.
One option available to you is to add method = [RequestMethod.GET]
(or any other verb) to the arguments of either the controller or method level RequestMapping
annotations. This is especially useful if you wish allow multiple methods for a given handler or controller.
However, SpringBoot provides another set of annotations for single-method handlers. These take the form of @<VERB>Mapping
.
@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
- etc
@Controller
@ResponseBody
@RequestMapping("/greeting")
class GreetingController {
@RequestMapping("/english", method = [RequestMethod.GET])
fun englishGreeting(): String = "Hello World!"
@GetMapping("/spanish")
fun spanishGreeting(): String = "¡Hola Mundo!"
}
Here you can see both options. They result in the same behaviour. Test this out by trying to hit either via a POST request and getting a Method ‘GET’ is not supported.
error.
curl -X POST http://localhost:8080/greeting/spanish
<stacktrace>.. "message":"Method 'GET' is not supported." ...
Summary
You have created a web controller, which is meant to handle web traffic.
In Part 3, we will create our first SpringBoot service, which will handle our business logic!