=== MVC API

The MVC API provides an annotation-driven alternative to the Script API for defining routes. Jooby uses an annotation processor to generate source code that defines and executes these routes. By default, the generated classes are suffixed with an underscore (`_`).

If you use Gradle 6.0 or later, or a modern Maven setup, Jooby leverages **incremental annotation processing**. This means the compiler only processes classes that have changed since the last build, significantly speeding up compilation times.

You can control incremental processing via compiler arguments:

.build.gradle
[source, groovy, role = "primary", subs="verbatim,attributes"]
----
tasks.withType(JavaCompile) {
    options.compilerArgs += [
        '-parameters',
        '-Ajooby.incremental=true'
    ]
}
----

.Kotlin (kapt)
[source, groovy, role = "secondary", subs="verbatim,attributes"]
----
kapt {
  arguments {
    arg('jooby.incremental', true)
  }
}
----

By setting `jooby.incremental=false`, you disable incremental processing entirely, forcing a full recompilation of the project every time. (Defaults to `true`).

The `io.jooby.annotation` package contains all the annotations available for MVC routes.

.MVC API Example:
[source,java,role="primary"]
----
import io.jooby.annotation.*;

@Path("/mvc")                  // <1>
public class Controller {

  @GET                         // <2>
  public String sayHi() {
    return "Hello Mvc!";
  }
}

public class App extends Jooby {
  {
    mvc(new Controller_());   // <3>
  }

  public static void main(String[] args) {
    runApp(args, App::new);
  }
}
----

.Kotlin
[source,kotlin,role="secondary"]
----
import io.jooby.annotation.*
import io.jooby.kt.runApp

@Path("/mvc")                  // <1>
class Controller {

  @GET                         // <2>
  fun sayHi(): String {
    return "Hello Mvc!"
  }
}

fun main(args: Array<String>) {
  runApp(args) {
    mvc(Controller_())        // <3>
  }
}
----

<1> Set a base path pattern. The `@Path` annotation can be applied at the class or method level.
<2> Define the HTTP method.
<3> Register the generated controller (`Controller_`) in the main application.

==== Getting Started

To quickly create a new MVC project, use the `jooby` console:

[source, bash]
----
jooby create myapp --mvc
----

The <<getting-started, Jooby CLI>> automatically configures the Maven/Gradle build and sets up the annotation processor for you.

==== Registration

Unlike some frameworks, Jooby **does not use classpath scanning**. MVC routes must be explicitly registered in your application configuration:

.Simple MVC Route Registration
[source, java, role = "primary"]
----
public class App extends Jooby {
  {
    mvc(new MyController_());
  }

  public static void main(String[] args) {
    runApp(args, App::new);
  }
}
----

.Kotlin
[source, kotlin, role = "secondary"]
----
import io.jooby.kt.runApp

fun main(args: Array<String>) {
  runApp(args) {
    mvc(MyController_())
  }
}
----

The javadoc:Jooby[mvc, io.jooby.MvcExtension] method installs the MVC routes. You can pass an instance directly, or if the controller constructor is annotated with `@Inject` (e.g., `jakarta.inject.Inject`), the generated code will attempt to resolve the dependencies from the registry.

==== Parameters

HTTP parameter extraction is handled via `@*Param` annotations.

You can also use the generic javadoc:annotation.Param[] annotation to extract a parameter from <<web-mvc-api-parameters-multiple-sources, multiple sources>> with a specific fallback order.

===== Header

Extract headers using the javadoc:annotation.HeaderParam[] annotation:

.Headers
[source, java, role = "primary"]
----
public class MyController {

  @GET
  public String handle(@HeaderParam String token) {  // <1>
    // ...
  }
}
----

.Kotlin
[source, kotlin, role = "secondary"]
----
class MyController {

  @GET
  fun handle(@HeaderParam token: String): String {  // <1>
    // ...
  }
}
----

<1> Accesses the HTTP header named `token`.

Unlike JAX-RS, specifying the parameter name inside the annotation is optional. Jooby infers it from the variable name. However, you must provide the name explicitly if the HTTP header is not a valid Java identifier:

.Invalid Java Identifier
[source, java, role = "primary"]
----
public class MyController {

  @GET
  public String handle(@HeaderParam("Last-Modified-Since") long lastModifiedSince) {
    // ...
  }
}
----

.Kotlin
[source, kotlin, role = "secondary"]
----
class MyController {

  @GET
  fun handle(@HeaderParam("Last-Modified-Since") lastModifiedSince: Long): String {
    // ...
  }
}
----

===== Cookie

Extract cookies using the javadoc:annotation.CookieParam[] annotation:

.Cookies
[source, java, role = "primary"]
----
public class MyController {

  @GET
  public String handle(@CookieParam String token) {  // <1>
    // ...
  }
}
----

.Kotlin
[source, kotlin, role = "secondary"]
----
class MyController {

  @GET
  fun handle(@CookieParam token: String): String {  // <1>
    // ...
  }
}
----

<1> Accesses the cookie named `token`.

As with headers, provide the explicit name if the cookie key contains dashes or invalid Java characters (e.g., `@CookieParam("token-id") String tokenId`).

===== Path

Extract path variables using the javadoc:annotation.PathParam[] annotation:

.PathParam
[source, java, role = "primary"]
----
public class MyController {

  @Path("/{id}")
  public String handle(@PathParam String id) {
    // ...
  }
}
----

.Kotlin
[source, kotlin, role = "secondary"]
----
class MyController {

  @Path("/{id}")
  fun handle(@PathParam id: String): String {
    // ...
  }
}
----

===== Query

Extract query string variables using the javadoc:annotation.QueryParam[] annotation:

.QueryParam
[source, java, role = "primary"]
----
public class MyController {

  @Path("/")
  public String handle(@QueryParam String q) {
    // ...
  }
}
----

.Kotlin
[source, kotlin, role = "secondary"]
----
class MyController {

  @Path("/")
  fun handle(@QueryParam q: String): String {
    // ...
  }
}
----

===== Formdata / Multipart

Extract form-data or multipart parameters using the javadoc:annotation.FormParam[] annotation:

.FormParam
[source, java, role = "primary"]
----
public class MyController {

  @POST("/")
  public String handle(@FormParam String username) {
    // ...
  }
}
----

.Kotlin
[source, kotlin, role = "secondary"]
----
class MyController {

  @POST("/")
  fun handle(@FormParam username: String): String {
    // ...
  }
}
----

===== Request Body

The HTTP request body does not require an explicit annotation. Simply define the POJO in the method signature:

.HTTP Body
[source, java, role = "primary"]
----
public class MyController {

  @POST("/")
  public String handle(MyObject body) {
    // ...
  }
}
----

.Kotlin
[source, kotlin, role = "secondary"]
----
class MyController {

  @POST("/")
  fun handle(body: MyObject): String {
    // ...
  }
}
----

===== Bind

The javadoc:annotation.BindParam[] annotation allows for custom data binding from the HTTP request directly into an object.

.Using the annotation
[source, java, role = "primary"]
----
public class Controller {

  @GET("/{foo}")
  public String bind(@BindParam MyBean bean) {
    return "with custom mapping: " + bean;
  }
}
----

.Kotlin
[source, kotlin, role = "secondary"]
----
class Controller {

  @GET("/{foo}")
  fun bind(@BindParam bean: MyBean) = "with custom mapping: $bean"
}
----

.Writing the mapping function
[source, java, role = "primary"]
----
public record MyBean(String value) {

  public static MyBean of(Context ctx) {
    // Build MyBean entirely from the Context
    return new MyBean(ctx.path("foo").value());
  }
}
----

.Kotlin
[source, kotlin, role = "secondary"]
----
class MyBean(val value: String) {

  companion object {
       @JvmStatic
       fun of(ctx: Context): MyBean {
          // Build MyBean entirely from the Context
          return MyBean(ctx.path("foo").value())
       }
  }
}
----

How `@BindParam` works:

* It looks for a public method/function on the target class that accepts a javadoc:Context[] and returns the target type.
* By default, it looks for this factory method on the parameter type itself (`MyBean`), but will fall back to searching the Controller class.

Alternatively, you can specify a distinct factory class and/or method name:
----
@BindParam(MyFactoryClass.class)
@BindParam(value = MyFactoryClass.class, fn = "fromContext")
----

===== Flash

Extract flash attributes using the javadoc:annotation.FlashParam[] annotation:

.Flash
[source, java, role = "primary"]
----
public class MyController {

  @GET
  public String handle(@FlashParam String success) {  // <1>
    // ...
  }
}
----

.Kotlin
[source, kotlin, role = "secondary"]
----
class MyController {

  @GET
  fun handle(@FlashParam success: String): String {  // <1>
    // ...
  }
}
----

<1> Accesses the flash attribute named `success`.

===== Session

Extract specific session attributes using the javadoc:annotation.SessionParam[] annotation:

.Session Attribute
[source, java, role = "primary"]
----
public class MyController {

  @GET
  public String handle(@SessionParam String userId) {  // <1>
    // ...
  }
}
----

.Kotlin
[source, kotlin, role = "secondary"]
----
class MyController {

  @GET
  fun handle(@SessionParam userId: String): String {  // <1>
    // ...
  }
}
----

<1> Accesses the session attribute named `userId`.

You can also request the entire javadoc:Session[] object:

.Session Object
[source, java, role = "primary"]
----
public class MyController {

  @GET
  public String handle(Session session) {  // <1>
    // ...
  }
}
----

.Kotlin
[source, kotlin, role = "secondary"]
----
class MyController {

  @GET
  fun handle(session: Session): String {  // <1>
    // ...
  }
}
----

<1> If no session exists yet, a **new session will be created**. To avoid this and only retrieve an existing session, use `Optional<Session>` as the parameter type.

===== Context

Extract specific context attributes using the javadoc:annotation.ContextParam[] annotation:

.Context Attribute
[source, java, role = "primary"]
----
public class MyController {

  @GET
  public String handle(@ContextParam String userId) {  // <1>
    // ...
  }
}
----

.Kotlin
[source, kotlin, role = "secondary"]
----
class MyController {

  @GET
  fun handle(@ContextParam userId: String): String {  // <1>
    // ...
  }
}
----

<1> Accesses the context attribute named `userId`.

You can also request all javadoc:Context[getAttributes, text="attributes"] at once:

.All Context Attributes
[source, java, role = "primary"]
----
public class MyController {

  @GET
  public String handle(@ContextParam Map<String, Object> attributes) {  // <1>
    // ...
  }
}
----

.Kotlin
[source, kotlin, role = "secondary"]
----
class MyController {

  @GET
  fun handle(@ContextParam attributes: Map<String, Any>): String {  // <1>
    // ...
  }
}
----

<1> To retrieve all context attributes, the parameter must be typed as a `Map<String, Object>` (or `Map<String, Any>` in Kotlin).

===== Multiple Sources

Use the javadoc:annotation.Param[] annotation to search for a parameter across multiple sources with an explicitly defined fallback order:

.Multiple Sources
[source, java, role = "primary"]
----
import static io.jooby.annotation.ParamSource.QUERY;
import static io.jooby.annotation.ParamSource.PATH;

public class FooController {

  @GET("/{foo}")
  public String multipleSources(@Param({ QUERY, PATH }) String foo) {
    return "foo is: " + foo;
  }
}
----

.Kotlin
[source, kotlin, role = "secondary"]
----
import io.jooby.annotation.ParamSource.QUERY
import io.jooby.annotation.ParamSource.PATH

class FooController {

  @GET("/{foo}")
  fun multipleSources(@Param(QUERY, PATH) foo: String) = "foo is: $foo"
}
----

If a request is made to `/bar?foo=baz`, the result will be `foo is: baz` because the `QUERY` parameter takes precedence over the `PATH` parameter in the annotation array.

==== Responses

===== Projections

The MVC module provides first-class support for Projections via annotations. This allows you to define the response view declaratively, keeping your controller logic clean and focused on data retrieval.

====== Usage

There are two ways to define a projection in an MVC controller.

You can annotate your method with `@Project` and provide the selection DSL:

.Via @Project Annotation
[source,java]
----
@GET
@Project("(id, name)")
public List<User> listUsers() {
  return service.findUsers();
}
----

Alternatively, you can define the projection directly within the HTTP method annotation (e.g., `@GET`, `@POST`) using the `projection` attribute:

.Via HTTP Method Attribute
[source,java]
----
@GET(value = "/users", projection = "(id, name, email)")
public List<User> listUsers() {
  return service.findUsers();
}
----

====== Automatic Wrapping

The Jooby Annotation Processor automatically handles the conversion of your method's return type. You are **not forced** to return a `Projected` instance; you can simply return your POJO or Collection, and Jooby will wrap it for you at compile-time.

However, if you need manual control (for example, to dynamically toggle validation), you can still return a `Projected` instance explicitly:

[source,java]
----
@GET
public Projected<User> getUser(String id) {
  User user = service.findById(id);
  return Projected.wrap(user)
      .failOnMissingProperty(true)
      .include("(id, status)");
}
----

[NOTE]
====
For more details on the Selection DSL syntax and available JSON engines, please refer to the <<core-responses-projections, Core Projections documentation>>.
====

===== Status Code

The default HTTP status code returned by an MVC route is `200 OK`, except for `void` methods annotated with `@DELETE`, which automatically return `204 No Content`.

If you need to return a different status code, you have two options:
1. Inject the javadoc:Context[] into your method and call javadoc:Context[setResponseCode, io.jooby.StatusCode].
2. Return a javadoc:StatusCode[] instance directly from the method.

===== NonBlocking

Any MVC method returning a non-blocking type (`CompletableFuture`, `Single`, `Maybe`, `Flowable`, `Mono`, `Flux`) is automatically handled as a non-blocking route.

Kotlin `suspend` functions are also supported natively:

.Kotlin Coroutines
[source, kotlin]
----
class SuspendMvc {
  @GET
  @Path("/delay")
  suspend fun delayed(ctx: Context): String {
    delay(100)
    return ctx.getRequestPath()
  }
}

fun main(args: Array<String>) {
  runApp(args) {
    mvc(SuspendMvc_())
  }
}
----

A non-blocking route runs on the event loop by default, where **blocking is NOT allowed**. For more details, see the <<core-responses-nonblocking, NonBlocking Responses>> section.

==== Execution Model

MVC routes follow the standard Jooby <<core-execution-model, Execution Model>>.

By default, if your route returns a blocking type (like a `String` or a POJO), Jooby automatically dispatches the execution to the **worker executor**. If it returns a non-blocking type (or is a `suspend` function), it runs on the **event loop**.

If you need explicit control over where a specific blocking MVC route executes, use the javadoc:annotation.Dispatch[] annotation:

.Dispatch Annotation
[source, java, role = "primary"]
----
public class MyController {

  @GET("/blocking")
  @Dispatch              // <1>
  public String blocking() {
    return "I'm blocking";
  }
}
----

.Kotlin
[source, kotlin, role = "secondary"]
----
import io.jooby.annotation.*

class MyController {

  @GET("/blocking")
  @Dispatch              // <1>
  fun blocking(): String {
    return "I'm blocking"
  }
}
----

<1> Forces the route to run in the `WORKER` executor, safely allowing blocking calls.

The javadoc:annotation.Dispatch[] annotation also supports routing execution to a named, custom executor:

.Dispatch to custom executor
[source, java, role = "primary"]
----
public class MyController {

  @GET("/blocking")
  @Dispatch("single")         // <1>
  public String blocking() {
    return "I'm blocking";
  }
}
----

.Kotlin
[source, kotlin, role = "secondary"]
----
import io.jooby.annotation.*

class MyController {

  @GET("/blocking")
  @Dispatch("single")         // <1>
  fun blocking(): String {
    return "I'm blocking"
  }
}
----

<1> Dispatches execution to the executor registered under the name `single`.

The custom executor must be registered in the application **before** the MVC route is registered:

.Custom executor registration
[source, java, role = "primary"]
----
{
  executor("single", Executors.newSingleThreadExecutor());

  mvc(new MyController_());
}
----

.Kotlin
[source, kotlin, role = "secondary"]
----
{
  executor("single", Executors.newSingleThreadExecutor())

  mvc(MyController_())
}
----

==== JAX-RS Annotations

Alternatively, you can use JAX-RS annotations to define MVC routes.

.Resource
[source, java, role="primary"]
----
import javax.ws.rs.GET;
import javax.ws.rs.Path;

@Path("/jaxrs")
public class Resource {

  @GET
  public String getIt() {
    return "Got it!";
  }
}
----

.Kotlin
[source, kotlin, role="secondary"]
----
import javax.ws.rs.GET
import javax.ws.rs.Path

@Path("/jaxrs")
class Resource {

  @GET
  fun getIt(): String {
    return "Got it!"
  }
}
----

These annotations work exactly like the Jooby native MVC annotations.

*(Note: Jooby does **not** implement the full JAX-RS specification, nor is there a plan to do so. Support for these annotations exists primarily to allow integration with third-party tools, like Swagger/OpenAPI generators, that rely on them).*

==== Generated Router

For each MVC controller class, a new class is generated ending with an underscore (`_`). This generated class mimics the constructors of the source class. (If the constructor is annotated with `@Inject`, a default constructor is automatically generated).

Any annotations found on the controller methods will be persisted as route attributes, unless explicitly excluded by the `jooby.skipAttributeAnnotations` compiler option.

You can access the generated routes at runtime:

[source, java]
----
{
  var routes = mvc(new MyController_());
  routes.forEach(route -> {
    // Modify or inspect the route
  });
}
----

==== Annotation Processor Options

[cols="2,1,1,4"]
|===
| Option | Type | Default | Description

| `jooby.debug`
| boolean
| true
| Runs the annotation processor in debug mode.

| `jooby.incremental`
| boolean
| true
| Hints to Maven/Gradle to perform incremental compilation. Essential for fast development iteration.

| `jooby.skipAttributeAnnotations`
| array
| []
| A comma-separated list of annotations to skip during bytecode generation (i.e., do not attach them as route attributes).

| `jooby.mvcMethod`
| boolean
| false
| Sets the `Route.mvcMethod` property on the generated route when true.

| `jooby.routerPrefix`
| string
|
| Adds a prefix to the generated class name.

| `jooby.routerSuffix`
| string
| _
| Sets the suffix for the generated class name.
|=== 

===== Setting Options

.Maven
[source, xml, role="primary", subs="verbatim,attributes"]
----
  <plugin>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
      <annotationProcessorPaths>
        <path>
          <groupId>io.jooby</groupId>
          <artifactId>jooby-apt</artifactId>
          <version>${jooby.version}</version>
        </path>
      </annotationProcessorPaths>
      <compilerArgs>
        <compilerArg>-Ajooby.debug=false</compilerArg>
        <compilerArg>-Ajooby.incremental=true</compilerArg>
        <compilerArg>-Ajooby.skipAttributeAnnotations=FooAnnotation,BarAnnotation</compilerArg>
      </compilerArgs>
    </configuration>
  </plugin>
----

.Gradle
[source, groovy, role="secondary", subs="verbatim,attributes"]
----
tasks.withType(JavaCompile) {
    options.compilerArgs += [
        '-parameters',
        '-Ajooby.debug=false',
        '-Ajooby.incremental=true',
        '-Ajooby.skipAttributeAnnotations=FooAnnotation,BarAnnotation'
    ]
}
----

[IMPORTANT]
====
The execution order of annotation processors is critical. If you are using `Lombok` and `Avaje Inject` alongside Jooby, the configuration order must be: `lombok` -> `avaje-inject` -> `jooby-apt`.
====
