Two things every handler was writing by hand. @Inject on a field is filled inside bind, before onInit, once per handler at boot: the request path still reads a field. The service is looked up by the field's exact declared type; a static or final field is refused, and a type nothing provides fails the boot naming the field. onInit stays for what has to be computed, or for a service that may not be there. BodyHandler<B> puts the body type in the signature — handle(req, res, body) — and leaves reading it to the format. bodyTypeOf resolves that type argument through a whole chain of bases, so tooling can read off a class what a route takes. @Consumes says in which media type, inherited from the base class that implements the reading, and is descriptive: the router does not enforce it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
flash-ext-jackson
Jackson JSON integration for Flash with an opinionated auto-marshal middleware.
What it provides
| Component | Description |
|---|---|
JacksonExtension |
Registers JSON services into FlashContext |
Json |
JSON read/write helper (body, bodyFrom, write, writeView) |
ObjectMapper |
Raw mapper escape hatch for advanced usage |
JacksonMiddleware |
autoJson() middleware for automatic outbound JSON marshalling |
Default mapper behavior (new JacksonExtension()):
- auto-discovers Jackson modules on classpath (
findAndAddModules()) - includes Java Time support (
jackson-datatype-jsr310) - writes date/time values as ISO-8601 strings (not numeric timestamps)
Recommended default
Install the extension, then apply autoJson() once at app or scope level.
JacksonExtension jackson = new JacksonExtension();
FlashApp app = FlashApp.create(8080)
.install(jackson)
.use(jackson.autoJson());
app.startAndBlock();
Behavior of autoJson():
- pass-through:
null,Response,byte[],String,CharSequence - any other return value: serialize to JSON
byte[] - sets
Content-Type: application/jsonfor marshalled responses - serialization failures throw
IllegalStateException
This keeps handlers concise while preserving Flash's direct byte write path.
Installation
<dependency>
<groupId>dev.relism</groupId>
<artifactId>flash-ext-jackson</artifactId>
<version>1.1-indev2</version>
</dependency>
Json helper API
Use Json when you want explicit, local control in a handler.
@POST("/users")
public final class CreateUser extends RequestHandler {
private Json json;
@Override
protected void onInit() {
json = require(Json.class);
}
@Override
public Object handle(Request req, Response res) throws Exception {
CreateUserBody body = json.body(req, CreateUserBody.class);
UserDto created = service.create(body);
res.status(201);
return json.write(res, created);
}
}
Methods:
body(req, Type.class)-> parse fromreq.body().bytes()bodyFrom(req, Type.class)-> parse fromreq.body().stream()write(res, obj)-> writes JSON string and sets JSON content typewriteView(res, obj, View.class)-> JSON with Jackson@JsonViewmapper()-> rawObjectMapper
Custom mapper
ObjectMapper mapper = JsonMapper.builder()
.addModule(new JavaTimeModule())
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.build();
FlashApp.create(8080)
.install(new JacksonExtension(mapper));
Scope usage
autoJson() works the same at scope level:
JacksonExtension jackson = new JacksonExtension();
app.mount("/api", api -> {
api.use(jackson.autoJson());
api.get("/health", (req, res) -> Map.of("ok", true));
});
If you need to pull it from context, JacksonMiddleware is also provided as a service
after the app boots (same lifecycle model as other extension-provided services).
Notes
- Install order is irrelevant (Flash two-phase extension lifecycle).
autoJson()and OpenAPI are intentionally decoupled.