URI Path Variables

URI variables can be referenced via method arguments. For example:

URI Variables Example

  1. import io.micronaut.http.annotation.Controller;
  2. import io.micronaut.http.annotation.Get;
  3. import io.micronaut.http.annotation.PathVariable;
  4. @Controller("/issues") (1)
  5. public class IssuesController {
  6. @Get("/{number}") (2)
  7. public String issue(@PathVariable Integer number) { (3)
  8. return "Issue # " + number + "!"; (4)
  9. }
  10. }

URI Variables Example

  1. import io.micronaut.http.annotation.Controller
  2. import io.micronaut.http.annotation.Get
  3. import io.micronaut.http.annotation.PathVariable
  4. @Controller("/issues") (1)
  5. class IssuesController {
  6. @Get("/{number}") (2)
  7. String issue(@PathVariable Integer number) { (3)
  8. "Issue # " + number + "!" (4)
  9. }
  10. }

URI Variables Example

  1. import io.micronaut.http.annotation.Controller
  2. import io.micronaut.http.annotation.Get
  3. import io.micronaut.http.annotation.PathVariable
  4. @Controller("/issues") (1)
  5. class IssuesController {
  6. @Get("/{number}") (2)
  7. fun issue(@PathVariable number: Int): String { (3)
  8. return "Issue # $number!" (4)
  9. }
  10. }
1The @Controller annotation is specified with a base URI of /issues
2The Get annotation is used to map the method to an HTTP GET with a URI variable embedded in the URI called number
3The method argument can be optionally annotated with PathVariable
4The value of the URI variable is referenced in the implementation

Micronaut will map the URI /issues/{number} for the above controller. We can assert this is the case by writing a set of unit tests:

Testing URI Variables

  1. import io.micronaut.context.ApplicationContext;
  2. import io.micronaut.http.client.HttpClient;
  3. import io.micronaut.http.client.exceptions.HttpClientResponseException;
  4. import io.micronaut.runtime.server.EmbeddedServer;
  5. import org.junit.AfterClass;
  6. import org.junit.BeforeClass;
  7. import org.junit.Test;
  8. import org.junit.jupiter.api.Assertions;
  9. import static org.junit.Assert.assertEquals;
  10. import static org.junit.Assert.assertNotNull;
  11. public class IssuesControllerTest {
  12. private static EmbeddedServer server;
  13. private static HttpClient client;
  14. @BeforeClass (1)
  15. public static void setupServer() {
  16. server = ApplicationContext.run(EmbeddedServer.class);
  17. client = server
  18. .getApplicationContext()
  19. .createBean(HttpClient.class, server.getURL());
  20. }
  21. @AfterClass (2)
  22. public static void stopServer() {
  23. if(server != null) {
  24. server.stop();
  25. }
  26. if(client != null) {
  27. client.stop();
  28. }
  29. }
  30. @Test
  31. public void testIssue() throws Exception {
  32. String body = client.toBlocking().retrieve("/issues/12"); (3)
  33. assertNotNull(body);
  34. assertEquals("Issue # 12!", body); (4)
  35. }
  36. @Test
  37. public void testShowWithInvalidInteger() {
  38. HttpClientResponseException e =Assertions.assertThrows(HttpClientResponseException.class, () ->
  39. client.toBlocking().exchange("/issues/hello"));
  40. assertEquals(400, e.getStatus().getCode()); (5)
  41. }
  42. @Test
  43. public void testIssueWithoutNumber() {
  44. HttpClientResponseException e = Assertions.assertThrows(HttpClientResponseException.class, () ->
  45. client.toBlocking().exchange("/issues/"));
  46. assertEquals(404, e.getStatus().getCode()); (6)
  47. }
  48. }

Testing URI Variables

  1. import io.micronaut.context.ApplicationContext
  2. import io.micronaut.http.client.HttpClient
  3. import io.micronaut.http.client.exceptions.HttpClientResponseException
  4. import io.micronaut.runtime.server.EmbeddedServer
  5. import spock.lang.AutoCleanup
  6. import spock.lang.Shared
  7. import spock.lang.Specification
  8. class IssuesControllerTest extends Specification {
  9. @Shared
  10. @AutoCleanup (2)
  11. EmbeddedServer embeddedServer = ApplicationContext.run(EmbeddedServer) (1)
  12. @Shared
  13. @AutoCleanup (2)
  14. HttpClient client = HttpClient.create(embeddedServer.URL) (1)
  15. void "test issue"() {
  16. when:
  17. String body = client.toBlocking().retrieve("/issues/12") (3)
  18. then:
  19. body != null
  20. body == "Issue # 12!" (4)
  21. }
  22. void "/issues/{number} with an invalid Integer number responds 400"() {
  23. when:
  24. client.toBlocking().exchange("/issues/hello")
  25. then:
  26. HttpClientResponseException e = thrown(HttpClientResponseException)
  27. e.status.code == 400 (5)
  28. }
  29. void "/issues/{number} without number responds 404"() {
  30. when:
  31. client.toBlocking().exchange("/issues/")
  32. then:
  33. HttpClientResponseException e = thrown(HttpClientResponseException)
  34. e.status.code == 404 (6)
  35. }
  36. }

Testing URI Variables

  1. import io.micronaut.context.ApplicationContext
  2. import io.micronaut.http.client.exceptions.HttpClientResponseException
  3. import io.micronaut.runtime.server.EmbeddedServer
  4. import io.kotlintest.shouldBe
  5. import io.kotlintest.shouldNotBe
  6. import io.kotlintest.shouldThrow
  7. import io.kotlintest.specs.StringSpec
  8. import io.micronaut.http.client.RxHttpClient
  9. class IssuesControllerTest: StringSpec() {
  10. val embeddedServer = autoClose( (2)
  11. ApplicationContext.run(EmbeddedServer::class.java) (1)
  12. )
  13. val client = autoClose( (2)
  14. embeddedServer.applicationContext.createBean(RxHttpClient::class.java, embeddedServer.getURL()) (1)
  15. )
  16. init {
  17. "test issue" {
  18. val body = client.toBlocking().retrieve("/issues/12") (3)
  19. body shouldNotBe null
  20. body shouldBe "Issue # 12!" (4)
  21. }
  22. "test issue with invalid integer" {
  23. val e = shouldThrow<HttpClientResponseException> { client.toBlocking().exchange<Any>("/issues/hello") }
  24. e.status.code shouldBe 400 (5)
  25. }
  26. "test issue without number" {
  27. val e = shouldThrow<HttpClientResponseException> { client.toBlocking().exchange<Any>("/issues/") }
  28. e.status.code shouldBe 404 (6)
  29. }
  30. }
  31. }
1The embedded server and http client is being started
2The server and client will be cleaned up after all of the tests have finished
3The tests sends a request to the URI /issues/12
4And then asserts the response is “Issue # 12”
5Another test asserts a 400 response is returned when an invalid number is sent in the URL
6Another test asserts a 404 response is returned when no number is provided in the URL. The variable being present is required in order for the route to be executed.

Note that the URI template in the previous example requires that the number variable is specified. You can specify optional URI templates with the syntax: /issues{/number} and by annotating the number parameter with @Nullable.

The following table provides some examples of URI templates and what they match:

Table 1. URI Template Matching
TemplateDescriptionMatching URI

/books/{id}

Simple match

/books/1

/books/{id:2}

A variable of 2 characters max

/books/10

/books{/id}

An optional URI variable

/books/10 or /books

/book{/id:[a-zA-Z]+}

An optional URI variable with regex

/books/foo

/books{?max,offset}

Optional query parameters

/books?max=10&offset=10

/books{/path:.*}{.ext}

Regex path match with extension

/books/foo/bar.xml