Skip to content

avaje/avaje-http

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Discord Build Maven Central javadoc javadoc License

HTTP server and client libraries via code generation.

A light (~80kb) wrapper to the JDK 11+ Java Http Client. Additionally, you can create Feign-style interfaces and have implementations generated via annotation processing.

  • Fluid API for building URLs and payload
  • JSON marshaling using Avaje Jsonb/Jackson/Gson
  • Light Feign-style interfaces via annotation processing.
  • Request/Response Interception
  • Authorization via Basic Auth or OAuth Bearer Tokens
  • Async and sync API

Use source code generation to adapt annotated REST controllers @Path, @Get, @Post, etc to Javalin, Helidon SE, and similar web routing HTTP servers.

  • Lightweight (65Kb library + generated source code)
  • Full use of Javalin or Helidon SE/Nima as desired
  • Bean Validation of request bodies supported (validation groups supported as well)

Add dependencies

<dependency>
  <groupId>io.avaje</groupId>
  <artifactId>avaje-http-api</artifactId>
  <version>${avaje.http.version}</version>
</dependency>

Add the generator module for your desired microframework as an annotation processor.

<!-- Annotation processors -->
<dependency>
  <groupId>io.avaje</groupId>
  <artifactId>avaje-http-{javalin/helidon}-generator</artifactId>
  <version>${avaje-http.version}</version>
  <scope>provided</scope>
</dependency>

JDK 23+

In JDK 23+, annotation processors are disabled by default, you will need to add a flag to re-enable.

<properties>
  <maven.compiler.proc>full</maven.compiler.proc>
</properties>

Define a Controller (These APT processors work with both Java and Kotlin)

package org.example.hello;

import io.avaje.http.api.Controller;
import io.avaje.http.api.Get;
import java.util.List;

@Controller("/widgets")
public class WidgetController {
  private final HelloComponent hello;
  public WidgetController(HelloComponent hello) {
    this.hello = hello;
  }

  @Get("/{id}")
  Widget getById(int id) {
    return new Widget(id, "you got it"+ hello.hello());
  }

  @Get()
  List<Widget> getAll() {
    return List.of(new Widget(1, "Rob"), new Widget(2, "Fi"));
  }

  record Widget(int id, String name){};
}

DI Usage

The annotation processor will generate controller adapters to register routes to Javalin/Helidon. The natural way to use the generated adapters is to get a DI library to find and wire them. The AP will automatically detect the presence of avaje-inject and generate the class to use avaje-inject's @Component as the DI annotation.

There isn't a hard requirement to use Avaje for dependency injection. In the absence of avaje-inject, the generated class will use @jakarta.inject.Singleton or @javax.inject.Singleton depending on what's on the classpath. Any DI library that can find and wire the generated @Singleton beans can be used. You can even use Dagger2 or Guice to wire the controllers if you so desire.

To force the AP to generate with @javax.inject.Singleton(in the case where you have both jakarta and javax on the classpath), use the compiler arg -AuseJavax=true

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-compiler-plugin</artifactId>
  <configuration>
    <compilerArgs>
      <arg>-AuseJavax=true</arg>
    </compilerArgs>
  </configuration>
</plugin>

Usage with Javalin

The annotation processor will generate controller classes implementing the AvajeJavalinPlugin interface, which we can register in javalin using:

List<AvajeJavalinPlugin> routes = ...; //retrieve using a DI framework

Javalin.create(cfg -> routes.forEach(cfg::registerPlugin)).start();

Usage with Helidon SE (4.x)

The annotation processor will generate controller classes implementing the Helidon HttpFeature interface, which we can register with the Helidon HttpRouting.

List<HttpFeature> routes = ... //retrieve using a DI framework
final var builder = HttpRouting.builder();

routes.forEach(builder::addFeature);

WebServer.builder()
         .addRouting(builder)
         .build()
         .start();

Generated sources

(Javalin) The generated WidgetController$Route.java is:

@Generated("avaje-javalin-generator")
@Singleton
public class WidgetController$Route implements Plugin {

  private final WidgetController controller;

  public WidgetController$Route(WidgetController controller) {
    this.controller = controller;
  }

  @Override
  public void apply(Javalin app) {

    app.get("/widgets/{id}", ctx -> {
      ctx.status(200);
      var id = asInt(ctx.pathParam("id"));
      var result = controller.getById(id);
      ctx.json(result);
    });

    app.get("/widgets", ctx -> {
      ctx.status(200);
      var result = controller.getAll();
      ctx.json(result);
    });

  }
}

(Helidon SE) The generated WidgetController$Route.java is:

@Generated("avaje-helidon-generator")
@Component
public class WidgetController$Route implements HttpFeature {

  private final WidgetController controller;

  public WidgetController$Route(WidgetController controller) {
    this.controller = controller;
  }

  @Override
  public void setup(HttpRouting.Builder routing) {
    routing.get("/widgets/{id}", this::_getById);
    routing.get("/widgets", this::_getAll);
  }

  private void _getById(ServerRequest req, ServerResponse res) throws Exception {
    res.status(OK_200);
    var pathParams = req.path().pathParameters();
    var id = asInt(pathParams.contains("id") ? pathParams.get("id") : null);
    var result = controller.getById(id);
    res.send(result);
  }

  private void _getAll(ServerRequest req, ServerResponse res) throws Exception {
    res.status(OK_200);
    var result = controller.getAll();
    res.send(result);
  }

}

Generated sources (Avaje-Jsonb)

If Avaje-Jsonb is detected, http generators with support will use it for faster Json message processing.

(Javalin) The generated WidgetController$Route.java is:

@Generated("avaje-javalin-generator")
@Component
public class WidgetController$Route implements Plugin {

  private final WidgetController controller;
  private final JsonType<List<Widget>> listWidgetJsonType;
  private final JsonType<Widget> widgetJsonType;

  public WidgetController$Route(WidgetController controller, Jsonb jsonB) {
    this.controller = controller;
    this.listWidgetJsonType = jsonB.type(Widget.class).list();
    this.widgetJsonType = jsonB.type(Widget.class);
  }

  @Override
  public void apply(Javalin app) {

    app.get("/widgets/{id}", ctx -> {
      ctx.status(200);
      var id = asInt(ctx.pathParam("id"));
      var result = controller.getById(id);
      widgetJsonType.toJson(result, ctx.contentType("application/json").outputStream());
    });

    app.get("/widgets", ctx -> {
      ctx.status(200);
      var result = controller.getAll();
      listWidgetJsonType.toJson(result, ctx.contentType("application/json").outputStream());
    });

  }
}

(Helidon SE) The generated WidgetController$Route.java is:

@Generated("avaje-helidon-generator")
@Component
public class WidgetController$Route implements HttpFeature {

  private final WidgetController controller;
  private final JsonType<WidgetController.Widget> widgetController$WidgetJsonType;
  private final JsonType<List<WidgetController.Widget>> listWidgetController$WidgetJsonType;

  public WidgetController$Route(WidgetController controller, Jsonb jsonb) {
    this.controller = controller;
    this.widgetController$WidgetJsonType = jsonb.type(WidgetController.Widget.class);
    this.listWidgetController$WidgetJsonType = jsonb.type(WidgetController.Widget.class).list();
  }

  @Override
  public void setup(HttpRouting.Builder routing) {
    routing.get("/widgets/{id}", this::_getById);
    routing.get("/widgets", this::_getAll);
  }

  private void _getById(ServerRequest req, ServerResponse res) throws Exception {
    res.status(OK_200);
    var pathParams = req.path().pathParameters();
    var id = asInt(pathParams.contains("id") ? pathParams.get("id") : null);
    var result = controller.getById(id);
    res.headers().contentType(MediaTypes.APPLICATION_JSON);
    //jsonb has a special accommodation for helidon to improve performance
    widgetController$WidgetJsonType.toJson(result, JsonOutput.of(res));
  }

  private void _getAll(ServerRequest req, ServerResponse res) throws Exception {
    res.status(OK_200);
    var result = controller.getAll();
    res.headers().contentType(MediaTypes.APPLICATION_JSON);
    listWidgetController$WidgetJsonType.toJson(result, JsonOutput.of(res));
  }

}