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));
}
}