Flak is a minimal but powerful framework for web applications and REST services. Its main philosophy is keeping boilerplate to a minimum. It runs either on the HttpServer embedded in the JDK, which costs no dependency at all, or on Netty.
Flak 3.0 and later require Java 17 or later. If you are stuck on an older JDK, the 2.x releases target Java 8.
It is composed of a generic API, two backends and some add-ons. In a minimal
setup, on top of the JDK backend, the total size of dependencies is around
60KiB. If you need to implement a REST server and handle JSON data, you will
have to add jackson-databind to your dependencies.
| Flak components | Description |
|---|---|
flak-api |
Public API |
flak-spi |
Internal API for service providers |
flak-backend-jdk |
Binding for the web server included in the JDK |
flak-backend-netty |
Binding for Netty, see Backends |
flak-login |
Add-on for managing authentication, see Authentication |
flak-jackson |
Add-on for conversion to/from JSON using Jackson, and for @QueryParams and @FormParams, see JSON |
flak-swagger |
Add-on to generate OpenAPI specifications, see OpenAPI |
flak-websocket |
Experimental add-on for websockets with the JDK backend, see WebSockets |
flak-util |
Misc utilities: CORS, route dumper |
Add the API and a backend to build.gradle (the badge above shows the latest
released version):
repositories {
maven { url = "https://jitpack.io" }
}
dependencies {
implementation "com.github.pcdv.flak:flak-api:3.2.1"
// the backend, see Backends for the netty alternative
runtimeOnly "com.github.pcdv.flak:flak-backend-jdk:3.2.1"
}The following application outputs "Hello world!" on http://localhost:8080:
public class HelloWorld {
@Route("/")
public String helloWorld() {
return "Hello world!";
}
public static void main(String[] args) throws Exception {
App app = Flak.createHttpApp(8080);
app.scan(new HelloWorld());
app.start();
Desktop.getDesktop().browse(new URI(app.getRootUrl()));
}
}Or if you like it compact:
public class HelloWorldCompact {
public static void main(String[] args) throws Exception {
Flak.createHttpApp(8080).scan(new Object() {
@Route("/")
public String helloWorld() {
return "Hello world!";
}
}).start();
}
}A slightly bigger taste, with a path variable, a query parameter and JSON:
@Route("/api/users/:id/orders")
@JSON
public List<Order> orders(String id, @QueryParam(value = "limit", defaultValue = "20") int limit) {
return store.orders(id, limit);
}
@Route("/api/users/:id/orders")
@Post
@JSON
public Order create(String id, Response r, Order order) {
r.setStatus(201);
return store.add(id, order);
}The documentation covers each of them in detail.
- Routing:
@Routeon any public method, one annotation per HTTP method (@Post,@Put,@Patch,@Delete,@Head,@Options), path variables (/users/:id) and splats (/files/*path), prefixes, and the routes of an app listed at runtime - Handler arguments: path variables, typed
@QueryParamwith defaults, objects built from the whole query string with@QueryParams,Query,Formand objects built from it with@FormParams,Request, and arguments of your own types built by custom extractors - Responses: return a
String, bytes, a stream or any object through a formatter, set the status and headers, redirect, stream chunked output or Server-Sent Events - Request bodies: streamed to the handler, with a size limit per app or per handler
- Errors and hooks: throw an
HttpExceptionto answer with a status, error and success handlers, a handler for unknown URLs, hooks before every request - Compression: gzip, per handler or class
- Several apps on one server, each under its own path, HTTPS, bind address, thread pool
- JSON:
@JSONconverts arguments and return values with Jackson - Authentication: sessions, login page,
@LoginRequired, permissions with@WithPermission, custom authentication schemes - Static resources:
app.serveDir()andapp.serveClasspath()serve files, optionally restricted to logged-in users - WebSockets, experimental: served on the routes of an app with the JDK backend, so that hooks and login apply to them, and dropped when the client is lost
- CORS: let the pages of other origins call the app, preflight requests answered before routing and login checks
- OpenAPI: generate a specification from the handlers
- Plugins: installed automatically or listed explicitly, and easy to write
- Two backends: the JDK's HttpServer or Netty, with the same code. Flak can also plug into a Netty server you own, next to websockets.
Java 17, a complete Netty backend, streamed request bodies with size limits,
route handlers that can be configured at runtime, typed query parameters,
explicit plugin lists, and experimental websockets on the JDK backend. A few
behaviours changed along the way, e.g. how + is decoded in query strings,
and which of 401 and 403 flak-login sends.
Migrating to 3.0 lists everything, with what to do
about it.
I'm a big fan of lightweight and simple. I've always liked the simplicity of Flask applications and missed an equivalent solution for Java. Most existing frameworks were very heavy in terms of dependencies (e.g. Play, Spring Boot, etc). Spark was a better fit but it brings ~2.5MiB of dependencies.
The JDK includes a HTTP server that is perfectly suited for serving small applications but its API is rather painful. Flak allows to leverage it with a friendly API, and the same application can run on Netty instead.
The API initially shared a lot of similarities with Flask:
- route handlers are methods with annotations like
@Route,@Post,@LoginRequiredetc. - the request can be accessed through a ThreadLocal
- user authentication is similar to flask-login
But now the style differs quite a bit since objects can be automatically passed in method arguments.
Building Flak requires a JDK 17 or later. Everything else, including the Gradle distribution itself, is downloaded by the wrapper:
./gradlew build
The test suite runs against both backends: ./gradlew :flak-tests:test for
the JDK one, ./gradlew :flak-tests:testNetty for Netty.
If your project uses the local Ivy repository, run:
./gradlew publish -Pversion=3.0-SNAPSHOT
If your project uses the local Maven repository, run:
./gradlew publishToMavenLocal -Pversion=3.0-SNAPSHOT
Then use version 3.0-SNAPSHOT in your project dependencies.