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)
<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>
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>
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){}; }
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>
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();
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("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); }); } }
@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.
@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()); }); } }
@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)); } }