The web framework
Java deserved_

Obsidian gives your Spark Java app a solid foundation — annotation routing, fluent migrations, LiveComponents, authentication, and more.

Get started in seconds

1 git clone https://github.com/obsidian-framework/skeleton
2 cd obsidian-skeleton
3 ./build.bat
App running on localhost:8888

Code that speaks for itself

Annotation routing

Declare routes directly on your methods. No manual wiring.

PostController.java
@Controller
public class PostController extends BaseController
{
    @GET("/posts")
    private Object index(PostRepository repo) {
        return render("posts.html",
            Map.of("posts", repo.findAll()));
    }
}

Fluent migrations

Laravel-inspired schema builder. No raw SQL.

CreatePostTable.java
public class CreatePostTable extends Migration
{
    @Override
    public void up() {
        createTable("posts", table -> {
            table.id();
            table.string("title").notNull();
            table.timestamps();
        });
    }
}

Authentication & roles

Protect routes with a single annotation.

AdminController.java
@HasRole("ADMIN")
@GET("/admin/dashboard")
private Object dashboard() {
    return render("dashboard.html");
}

@CsrfProtect
@POST("/login")
private Object login(Request req, Response res) {
    login(username, password, session);
}

Form validation

Simple, readable validation rules.

PostController.java
ValidationResult result =
    RequestValidator.validateSafe(req,
        Map.of(
            "title", "required|min:20",
            "body",  "required|min:100"
        ));

if (result.fails()) {
    return render("form.html",
        Map.of("errors", result.getErrors()));
}

One backend, every frontend

One backend.
Any interface_

Obsidian doesn't force your frontend choices. Ship reactive UIs with LiveComponents, classic server-rendered pages with Pebble, or a pure JSON API consumed by any external stack.

Counter.java
@LiveComponentImpl
public class Counter extends LiveComponent
{
    @State
    private int count = 0;

    public void increment() { count++; }
    public void decrement() { count--; }

    public int getCount() { return count; }

    public String template() {
        return "components/counter.html";
    }
}
counter.html
<div live:id="">
    <h2>Count: </h2>
    <button live:click="increment">+</button>
    <button live:click="decrement">-</button>
</div>

Everything you need

LiveComponents

Reactive server-side UI without writing a single line of JavaScript. Inspired by Laravel Livewire.

Middleware system

@Before / @After with built-in CORS, rate limiting, logging, and API key middlewares.

Repository injection

Annotate a class with @Repository and it's automatically injected into your controller methods.

CSRF protection

One annotation on your POST routes. Token generation and validation handled automatically.

Flash messages

redirectWithFlash() for temporary success and error notifications without external dependencies.

Server-Sent Events

Real-time streaming from server to client. No WebSocket complexity when you don't need it.

Examples