惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
V
V2EX
S
SegmentFault 最新的问题
Apple Machine Learning Research
Apple Machine Learning Research
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
T
Tailwind CSS Blog
爱范儿
爱范儿
雷峰网
雷峰网
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
博客园 - Franky
小众软件
小众软件
Hugging Face - Blog
Hugging Face - Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
阮一峰的网络日志
阮一峰的网络日志
博客园 - 聂微东
Jina AI
Jina AI
V
Visual Studio Blog
博客园 - 【当耐特】
Last Week in AI
Last Week in AI
大猫的无限游戏
大猫的无限游戏
博客园_首页
The Cloudflare Blog
罗磊的独立博客
美团技术团队
P
Proofpoint News Feed
S
Securelist
K
Kaspersky official blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
NISL@THU
NISL@THU
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
The Hacker News
The Hacker News
C
Cisco Blogs
AWS News Blog
AWS News Blog
Know Your Adversary
Know Your Adversary
P
Privacy International News Feed
月光博客
月光博客
Simon Willison's Weblog
Simon Willison's Weblog
Project Zero
Project Zero
Cyberwarzone
Cyberwarzone
L
LINUX DO - 最新话题
C
CERT Recently Published Vulnerability Notes
Help Net Security
Help Net Security
T
Threat Research - Cisco Blogs
博客园 - 叶小钗
S
Secure Thoughts
有赞技术团队
有赞技术团队
博客园 - 司徒正美
宝玉的分享
宝玉的分享
Hacker News - Newest:
Hacker News - Newest: "LLM"
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO

Hacker News: Show HN

PurrrrrFocus: Pomodoro Timer App - App Store Workflow Engine — Multi-Step Orchestration for Bun RapidPhoto: Pro Photo Editor App - App Store GitHub - DheerG/swarms: Achieve extraordinary results with claude code across a variety of tasks SPICE simulation → oscilloscope → verification with Claude Code — Lucas Gerads Show HN: VCoding – A 5 MB native Windows IDE with no dynamic dependencies Show HN: LLMs don't hallucinate because they're bad at math, it's the format GitHub - Agent-FM/agentfm-core: AgentFM is a peer-to-peer network that turns everyday computers into a decentralized AI supercomputer. AgentFM lets you run massive AI workloads directly across a global mesh of idle CPUs and GPUs. Show HN: Tracking Top US Science Olympiad Alumni over Last 25 Years GitHub - Potarix/agent-hub: One place to talk to all your agents Show HN: Runtime security for AI agents(injection,tool abuse, data exfiltration) GitHub - dubeyKartikay/lazyspotify: Terminal Spotify client for macOS and Linux GitHub - the-banana-tool/king-louie: Easy to use GUI Personal AI Assistant. Win/Linux/Mac. Show HN I made my vacation rental bookable by AI agents–no Airbnb, 0% commission GitHub - basteez/jsf-autoreload: maven plugin to enable hot reload on jsf projects uvm32/hosts/host-gdbstub at main · ringtailsoftware/uvm32 GitHub - labsai/EDDI: Config-driven engine that turns JSON into production-grade AI agents. Multi-agent orchestration, 12+ LLM providers, MCP/A2A protocols, RAG, persistent memory, and enterprise compliance (EU AI Act, GDPR, HIPAA). Built on Quarkus. GitHub - glitchnsec/fortyone-oss: AI Executive Assistant Platform Quickstart | Alien GitHub - muxshed/shed: One stream in, or many. Every destination, simultaneously. No cloud middleman, no per-channel fees, no limits. GitHub - ocrbase-hq/ocrbase: 📄 PDF/IMG ->.MD/JSON Document OCR API for PaddleOCR and GLMOCR. Self-hostable. GitHub - impactjo/home-memory: MCP server that lets your AI assistant remember everything about your home. GitHub - Sets88/dbcls: DbCls is a powerful terminal database client that supports various databases GitHub - neptun2000/heor-agent-mcp GitHub - SeanFDZ/macmind: Single-layer transformer in HyperTalk for the classic Macintosh RollQuation: Math Puzzles - Apps on Google Play GitHub - dropbox/witchcraft Show HN: Agent-cache – Multi-tier LLM/tool/session caching for Valkey and Redis GitHub - opentalon/opentalon: OpenTalon is an open-source platform built from the ground up in Go as a robust alternative to OpenClaw LinkedIn™ 职位抓取工具 - Chrome 应用商店 GitHub - EdoardoBambini/Agent-Armor-Iaga: AI agents are getting tool access — shell, file system, databases, APIs, secrets. But **nobody is governing what they actually do with it**. Frameworks like LangChain, CrewAI, AutoGen, and Claude Code give agents the power to execute. Agent Armor gives you the power to control, audit, and approve every single action before it happens. HN Vibes — Week 15, Apr 7–13 2026 GitHub - chojs23/ec: Easy terminal-native 3-way git mergetool vim-like workflow GitHub - SethPyle376/hiraeth: Local AWS emulator focused on fast integration testing, with SQS support, SQLite-backed state, and a debug-friendly web UI. GitHub - JakOb-dotcom/cloud-sandbox-security-analysis: Technical analysis and Proof of Concept (PoC) regarding environment variable exfiltration in containerized cloud sandboxes via side-channel data leaks. Springboards - Flint Alpha Show HN: A simpler coding agent harness GitHub - audiodude/sudomake-friends GitHub - 256thFission/mini-mythos: OSS clone of Anthropic’s Mythos harness to locate C/C++ memory vulnerabilities Show HN: OpenParallax: OS-level privilege separation for AI agent execution Hacker News Sorted - Chrome 应用商店 Show HN: How to Install Docker on Ubuntu 24.04 LTS: Complete 2026 Guide GitHub - himanshudongre/smriti GitHub - sverrirsig/claude-control: macOS desktop dashboard for monitoring and managing multiple Claude Code sessions GitHub - ory/dockertest: Write better integration tests! Dockertest helps you boot up ephermal docker images for your Go tests with minimal work. Chiral - Chrome 应用商店 Show HN: Two Claudes collaborating through shared memory on a $100 mini-PC GitHub - pmichaillat/latex-cv: Minimalist LaTeX template for academic CVs GitHub - oguzbilgic/posse: A web UI for Anthropic Managed Agents. GitHub - sshiraz/depsly: Dependency risk analysis tool for npm packages ABI Add safari/agent-harness — Safari browser automation via safari-mcp by achiya-automation · Pull Request #212 · HKUDS/CLI-Anything GitHub - Halfblood-Prince/trustcheck: Verify PyPI package attestations and improve Python supply-chain security GitHub - oguzbilgic/kern-ai: Agents that do the work and show it. GitHub - bruits/satteri: High-performance Markdown and MDX processing for the JavaScript ecosystem GitHub - tylergibbs1/feedstock: High-performance web crawler and scraper for TypeScript, powered by Bun and Playwright GitHub - Grimm67123/grimmbot: The self-improving sandboxed and open-source AI agent. With persistent memory and scheduling. GitHub - whitevanillaskies/whitebloom: Local whiteboard that blooms. GitHub - hwdsl2/docker-whisper: Docker image for a self-hosted Whisper speech-to-text server with speaker diarization and OpenAI-compatible transcription and translation APIs. Powered by faster-whisper. Supports all Whisper models, NVIDIA GPU (CUDA) acceleration, JSON/SRT/VTT output, SSE streaming, offline mode, and multi-arch (amd64, arm64). GitHub - yisding/reviewwiggum GitHub - MarwanAlsoltany/serrors: Structured errors for Go: sentinel hierarchies, typed data, custom formatting, and slog integration. GitHub - soatok/age-php GitHub - Luthiraa/markitme GitHub - stagas/rtdiff: realtime git diff gui and AI-assisted commits GitHub - tombedor/excalicharts GitHub - wh1le/excalidraw-edit: Open and edit .excalidraw files from the terminal. Offline, auto-saves to disk. MalExt Sentry - Malicious Extension Scanner - Chrome 应用商店 GitHub - syi0808/asciianimesvg: Generate animated ASCII art SVGs from text. CLI, Rust library, WASM, and web editor. GitHub - zaina-ml/ml_forge: A visual-based graph node editor for training computer vision models. GitHub - anakin87/llm-rl-environments-lil-course: 🌱 A little course on Reinforcement Learning Environments for evaluating and training Language Models GitHub - takaakit/superpowers-uml: Superpowers-UML modifies Superpowers to ensure a software development workflow in which AI agents design through UML modeling. AdriByte Studio - Sviluppo Web e Soluzioni Digitali GitHub - chouligi/angel-copilot: Your personalized Angel Investment Advisor Show HN: MoodSense AI (ML and FastAPI and Gradio, Deployed on Hugging Face) Moodsense Ai - a Hugging Face Space by aman179102 GitHub - agenteractai/lodmem: Level Of Detail Context Management for Agents GitHub - ostefani/subnetlens: A fast, concurrent network scanner with a TUI and plain-text CLI, built in Go. It discovers live hosts on your network, scans their open ports, resolves hostnames, and fingerprints operating systems—delivered. Cyber Pulse: Agentic Intel - Apps on Google Play Whisper API: Self-Hostable Speech to Text Transcription The Agent-Web Protocol Stack: A Research Thesis GitHub - msmarkgu/RelayFreeLLM: A restful API designed to route user prompts to various AI model providers. Show HN: Provepy – A Python decorator that proves your code using Lean and LLMs Show HN: Pardonned.com – A searchable database of US Pardons GitHub - patrickdappollonio/dux: Dux is a terminal UI that lets you run multiple AI coding agents side by side, each in its own git worktree, with full companion terminals, macros, commit generation, and a command palette that knows more tricks than you do. kMC Crystal Simulator Show HN: HyperFlow – A self-improving agent framework built on LangGraph GitHub - stef41/vibescore: 🎵 Grade your vibe-coded project. One command, instant letter grade across security, quality, dependencies, and testing. GitHub - stef41/lmscan: 🔍 Detect AI-generated text and fingerprint which LLM wrote it. Open-source GPTZero alternative. Zero dependencies, works offline. imgur.com GitHub - visionscaper/collabmem: Enabling long-term collaboration with Agentic AI - building up episodic and world model memory over time with in-context awareness 在 Steam 上购买 FriedrichAI: Offline AI 立省 10% GitHub - atripati/ark: AI Runtime Kernel — a context operating system for AI agents. Eliminates tool bloat, loads only what’s needed, and gives LLMs their reasoning space back. GitHub - nowork-studio/toprank: Open-source Claude Code skills for SEO, SEM, Google Ads GitHub - tacomanator/sash: Lightweight macOS menu bar app for reliably cycling through windows of the current application. Appents | Social Media Management for Product-First Teams GitHub - pnhoang/youtube-spam-blocker: Automatically detects and hides spam messages in YouTube Live chat. Set rate limits, keyword filters, and block repeat offenders. GitHub - decisionnode/DecisionNode: CLI + Local MCP - A shared structured memory store across Claude Code, Cursor, Windsurf, Antigravity, and every MCP client. Semantically queryable. GitHub - AvaCodeSolutions/django-email-learning: An open source Django app for creating email-based learning platforms with IMAP integration and React frontend components. The $100K Gap in Kubernetes Security Tooling Function Calling Harness: From 6.75% to 100%
GitHub - Petersoj/jet: A simple, lightweight, modern, turnkey, Java web client and server library
Petersoj · 2026-06-16 · via Hacker News: Show HN

Table of Contents

  • Introduction
  • Modules
    • Common
      • Installation
      • Guide
        • Header Models
        • Header Data Structures
        • Enums
        • URL Building
        • I/O Utilities
        • Other Utilities
    • Server
      • Installation
      • Guide
        • JetServer.Builder Configuration
        • Handle
          • Exceptions
        • Useful Handler Implementations
          • Directory
          • Redirect
        • Router Implementations
        • Route Implementations
        • Session & SessionStore Implementations
        • Let's Encrypt SSL Certificates
    • OpenAPI Annotations
      • Installation
      • Guide
    • OpenAPI Annotations Plugin
      • Installation
      • Guide
        • Example Configuration (build.gradle.kts)
        • Why not an annotation processor?
    • Client
  • Personal Note About AI
  • Alternatives

Introduction

Jet is a simple, lightweight, modern, turnkey, Java web client and server library.

Jet is a wrapper around the excellent Jetty web client and server library. Jetty provides the battle-tested low-level protocol handling, while Jet focuses on providing a modern and consistent interface with superb documentation and an amazing developer experience.

Jet offers four modules: Common, Server, OpenAPI Annotations, OpenAPI Annotations Plugin, and Client.

A few more awesome things about Jet:

  • Exhaustive Javadoc documentation (all public classes, fields, and methods have Javadocs)
  • Amazing developer experience
  • High quality modern Java code (no AI slop)
  • No runtime annotation magic
  • A library, not a framework (structure your codebase however you prefer)
  • Lightweight with minimal dependencies
  • Wide code coverage via unit testing
  • Releases are built directly from this source repository using GitHub Actions CI
  • Kotlin friendly
  • MIT licensed

Give this repository a star ⭐ and consider sponsoring ❤️

Modules

Common

The common module for various Jet modules.

This module contains many useful model classes and utilities for all your web server and client needs. For example, instead of crafting a response cookie header value using manual string concatenation, you can use Cookie.builder().name("name").value("value").httpOnly().secure().build().toResponseString(). For another example, instead of crafting a URL string using manual string concatenation, you can use Url.builder().scheme(HTTPS).host("example.com").addQueryParameter("key", "value").build().toString(). There are many header models supported, such as Content-Security-Policy, ETag, ContentEncoding. There are many enums supported, such as Method (e.g. GET, POST), Status (e.g. 404 Not Found), Version (e.g. HTTP/2), and more. All of these classes exist in an effort to improve developer experience, increase type-safety and single-source-of-truth (e.g. no scattered variables or duplicate string constants), and decrease bugs. See the guide for all models classes and utilities this module provides.

Installation

This module is transitively depended on by the Server and Client modules, so you typically don't need to install this module directly.

For build.gradle.kts:

dependencies {
    implementation("net.jacobpeterson.jet:common:3.3.0")
}

For build.gradle:

dependencies {
    implementation 'net.jacobpeterson.jet:common:3.3.0'
}

For pom.xml:

<dependency>
    <groupId>net.jacobpeterson.jet</groupId>
    <artifactId>common</artifactId>
    <version>3.3.0</version>
</dependency>

Guide

Most classes in this module are immutable and follow the builder pattern for creation.

Header Models

Header Data Structures

Case-insensitive key-value pairs:

Enums

URL Building

I/O Utilities

Other Utilities

A simple, lightweight, modern, turnkey, Java web server library.

Features:

  • HTTP/1, HTTP/1.1, HTTP/2
  • HTTPS encryption with SSL/TSL certificate hot-swap reloading
  • Custom routing and handlers
  • Sessions
  • Resource serving (classpath files, filesystem files, InputStream)
  • Server-Sent Events (SSE)
  • WebSockets (coming soon)
  • Multipart request body
  • Response body compression (Zstandard, Brotli, Gzip, Deflate)
  • Virtual threads (no more ugly async/reactive programming)

Installation

For build.gradle.kts:

dependencies {
    implementation("net.jacobpeterson.jet:server:3.3.0")
}

For build.gradle:

dependencies {
    implementation 'net.jacobpeterson.jet:server:3.3.0'
}

For pom.xml:

<dependency>
    <groupId>net.jacobpeterson.jet</groupId>
    <artifactId>server</artifactId>
    <version>3.3.0</version>
</dependency>

Guide

Here's a very simple example to get you going:

static void main() {
    JetServer.builder()
            .sslLetsEncrypt() // Enable Let's Encrypt SSL/TLS
            .sessionStore() // Enable in-memory sessions
            .router(ImmutableSimpleRouter.builder()
                    // Add a custom handler
                    .addLast(PathExactRoute.builder().path("/custom-path").build(), handle ->
                            handle.getResponse().responseHtml("<h1>Custom handler!</h1>"))
                    // Serve files from `~/webroot/`
                    .addLast(PathStartsWithRoute.builder().path("/").build(), FileDirectoryHandler
                            .simpleMutable(Path.of(System.getProperty("user.home"), "webroot"), null, true))
                    .build())
            .build(); // Automatically starts the server and adds a JVM shutdown hook to stop the server gracefully
}

JetServer represents the web server instance. Use the builder to configure and start the web server.

JetServer.Builder Configuration

Handle

Handle is a class that represents a web server request and response. It has getters for the Request and Response objects. Features: multipart request bodies, dynamic transparent response compression config, Resource serving, SSE, and more. For HTML templating, Jte is recommended as it provides the best features with the best type-safety out of all major Java templating engine libraries.

The Handler functional interface is provided a Handle instance for the current HTTP request/response lifecycle. Using a functional interface makes it very easy to wrap/nest Handler implementations, which is the mechanism for implementing middleware.

Exceptions

StatusException is thrown by some methods for 400 Bad Request (for example if a request header is malformed) and is used as a silent Exception for breaking the flow of execution in an implementation handler and returning a response status code.

BodyStreamException is thrown by some methods for request/response body streaming client timeouts and early disconnects.

The Handler should not consume these exceptions. JetServer expects these exceptions to be thrown for special processing (e.g. to set the response status upon a StatusException or to not log an error for BodyStreamException).

Useful Handler Implementations

Directory

Handlers for serving files from the classpath or filesystem with support for relativizing request paths, serving a default file (like /index.html) for request paths representing a directory, serving a default file for requests paths representing a file without an extension (like /index), redirecting to default files or default extensions, applying a ResponseCacheControl, and caching Resource instances from with automatic cache invalidation using WatchService:

Redirect

Simple handlers for common redirection cases:

Router Implementations

These Router implementations represent a priority list for registered Route instances. Only one Route will be matched for a request/response lifecycle.

Route Implementations

These Route implementations are added to the Router and tested for a match when a request is received.

Session & SessionStore Implementations

HTTP sessions store data server-side on behalf of a client. The client stores a cookie to be sent to the server to reference the stored server-side data. These simple implementations store data in memory and have sensible defaults to get you started with HTTP sessions:

A recommended, but more advanced, implementation is to use Redis/Valkey via Redisson with RLO for better type-safety and persisting session data after a server restart.

Let's Encrypt SSL Certificates

JetServer has native support for Let's Encrypt SSL certificates. Follow these steps to add HTTPS support to your web server for free. This guide assumes you're using an Ubuntu Linux VM hosted on an AWS EC2 instance, but can be adapted to other Linux distributions.

  1. Install Java Amazon Corretto 25 for an unprivileged user named jet:

    • Via SDKMAN!:
      sudo useradd -ms /bin/bash jet
      sudo apt install zip
      # https://sdkman.io
      curl -s "https://get.sdkman.io" | sudo -u jet bash
      sudo -iu jet bash -ic "sdk install java 25.0.3-amzn"
    • Via Apt repositories:
      sudo useradd -ms /bin/bash jet
      # https://docs.aws.amazon.com/corretto/latest/corretto-25-ug/generic-linux-install.html
      wget -O - https://apt.corretto.aws/corretto.key | \ 
      sudo gpg --dearmor -o /usr/share/keyrings/corretto-keyring.gpg
      echo "deb [signed-by=/usr/share/keyrings/corretto-keyring.gpg] https://apt.corretto.aws stable main" | \ 
      sudo tee /etc/apt/sources.list.d/corretto.list
      sudo apt-get update
      sudo apt-get install -y java-25-amazon-corretto-jdk
  2. Install certbot and add a renewal hook that allows users in the letsencrypt group to read certificate files:

    sudo snap install certbot --classic
    sudo groupadd letsencrypt
    sudo usermod -aG letsencrypt jet
    sudo mkdir -p /etc/letsencrypt/renewal-hooks/deploy/
    sudo tee /etc/letsencrypt/renewal-hooks/deploy/change-permissions.sh <<EOF
    #!/bin/bash
    chgrp -R letsencrypt /etc/letsencrypt/{live,archive}
    chmod -R g+rX /etc/letsencrypt/{live,archive}
    EOF
    sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/change-permissions.sh
  3. Update your DNS A (IPv4) and AAAA (IPv6) records to point to your server. Ensure your AWS EC2 Security Group allows ports 80 and 443 from any IPv4 or any IPv6 address. Configure nftables (successor to the iptables) to redirect privileged ports 80 and 443 to the unprivileged default web server ports 8080 and 8443:

    sudo tee -a /etc/nftables.conf <<EOF
    table inet nat {
        chain prerouting {
            type nat hook prerouting priority dstnat; policy accept;
            tcp dport { 80, 443 } redirect to tcp dport map { 80 : 8080, 443 : 8443 }
        }
        chain output {
            type nat hook output priority dstnat; policy accept;
            fib daddr type local tcp dport { 80, 443 } redirect to tcp dport map { 80 : 8080, 443 : 8443 }
        }
    }
    EOF
    sudo systemctl enable --now nftables
  4. In your Maven or Gradle project with the Server module installed, use FileDirectoryHandler to serve a webroot for the certbot certificate renewal process and run your Maven or Gradle project as the jet user on your server:

    sudo -iu jet bash -ic "mkdir webroot"
    JetServer.builder()
         .router(ImmutableSimpleRouter.builder()
                 .addLast(PathStartsWithRoute.builder().path("/.well-known/acme-challenge/").build(),
                         FileDirectoryHandler.simpleMutable(Path.of("webroot"), null, true))
                 .build())
         .build();
  5. Now request your SSL certificate: sudo certbot certonly -d <your_domain> --webroot /home/jet/webroot/ (specify /home/jet/webroot/ as the webroot)

  6. Now configure JetServer.Builder with .sslLetsEncrypt():

    JetServer.builder()
            .sslLetsEncrypt() // Automatically enables hot-swap SSL reloading
            .router(ImmutableSimpleRouter.builder()
                    // Keep the `FileDirectoryHandler` for future `certbot` auto-renewals.
                    .addLast(PathStartsWithRoute.builder().path("/.well-known/acme-challenge/").build(),
                            FileDirectoryHandler.simpleMutable(Path.of("webroot"), null, true))
                    .addLast(PathExactRoute.builder()
                            .schemeEnum(HTTPS)
                            .path("/")
                            .build(), handle -> handle.getResponse().responseText("HTTPS yay!"))
                    .build())
            .build();

Because of Jet Server's native support for SSL/TLS, it is generally not recommended to put Jet Server behind a reverse proxy (such as Nginx) to handle SSL/TLS termination. Unless you have other web server requirements, like using PHP FPM via Nginx to support PHP applications, you should use Jet Server as the main web server on your Linux VM.

OpenAPI Annotations

A code-first OpenAPI specification annotations library.

This module provides Java annotations for all OpenAPI objects defined within the OpenAPI Description. This allows you to define OpenAPI specifications directly within Java code using Java annotations so that your API specification lives alongside the API implementation handlers. This module is built in a way that enforces type-safety and encourages single-source-of-truth within API specifications. For example, instead of declaring the path "/api/account/profile-picture" separately in your OpenAPI spec, in the Java web server handler code, and in the client API request code (e.g. a JavaScript fetch() call), you can declare the path once as a string constant in Java, reference that same constant within an OpenAPI annotation, generate an OpenAPI spec from those annotations, and then generate a client library from that OpenAPI spec. This way, everything is type-safe and dervied from a single-source-of-truth, which reduces bugs caused by typos, forgetting to update request paths and JSON schemas, etc.

One of the best features this module offers is OpenApiSchema.fromClass() which enables you to include JSON schemas generated directly from Java classes in your OpenAPI specification, which is the pinnacle of type-safety and single-source-of-truth! The enables you to create a strong client-server API contract in which the Java server defines the contract that all generated OpenAPI clients adhere to. The excellent victools/jsonschema-generator library is used to generate the JSON schemas in the OpenAPI Annotations Plugin, so generics, inheritance, nullability, etc. are all supported! Note that you can enforce @Nullable annotations on the server in implementation handlers using NullableUtil.requireNonNullFieldsSet().

Installation

For build.gradle.kts:

dependencies {
    implementation("net.jacobpeterson.jet:openapi-annotations:3.3.0")
}

For build.gradle:

dependencies {
    implementation 'net.jacobpeterson.jet:openapi-annotations:3.3.0'
}

For pom.xml:

<dependency>
    <groupId>net.jacobpeterson.jet</groupId>
    <artifactId>openapi-annotations</artifactId>
    <version>3.3.0</version>
</dependency>

Guide

All OpenAPI objects defined within the OpenAPI description have been implemented, enabling you to define an OpenAPI specification using Java annotations with no limitations:

There are few quirks when working with Java annotations. First, annotation methods/values cannot be set to null, so some OpenAPI annotation methods have been marked with the special annotation AnnotationArrayIsNullableValue to denote that the array represents a nullable value, meaning null is defined as an empty array and non-null is defined as an array with a single element. Second, to support maps/dictionaries, some OpenAPI annotation methods have been marked with the special annotation AnnotationArrayIsMap to denote that the array represents a map with annotation entries that uses the special annotation AnnotationArrayIsMapKey to denote the map key.

Note that some OpenAPI annotations encourage the use of the models and enums from Common module. For example, OpenApiPathItem.MethodEntry has both key which can be set to a String constant, and keyEnum which can be set to a Method enum. However, an unfortunate limitation of Java annotations with methods of enum types is that they cannot be set to constant references and must be set to the enum constant directly. This makes implementing the single-source-of-truth practice a bit harder, since, for example, you cannot declare the constant public static final Method METHOD = Method.GET and use that constant in a type-safe way within the annotation declaration and for the web server implementation handler route registration. A workaround is to use String references instead, which Java allows enum methods to reference. The Common module provides a ToString inner class for all enums so they can be used within Java annotations as constant references. For example, public static final Method METHOD = Method.GET becomes public static final String METHOD = Method.ToString.GET. Now this string constant can be directly referenced both by the OpenAPI annotation and the web server implementation handler route registration, so we keep our type-safety and single-source-of-truth practice!

Here is an example of the recommended approach for using OpenAPI annotations and implementation handlers with Jet using type-safety and single-source-of-truth practices. Note that this example uses static inner classes for Web and AccountHandlers, but ideally these would be separate class files, to better manage separation of concerns and to not include so much code in one file.

import lombok.Builder;
import lombok.Getter;
import lombok.Value;
import net.jacobpeterson.jet.openapiannotations.OpenApi;
import net.jacobpeterson.jet.openapiannotations.OpenApiInfo;
import net.jacobpeterson.jet.openapiannotations.OpenApiMediaType;
import net.jacobpeterson.jet.openapiannotations.OpenApiOperation;
import net.jacobpeterson.jet.openapiannotations.OpenApiParameter;
import net.jacobpeterson.jet.openapiannotations.OpenApiPathItem;
import net.jacobpeterson.jet.openapiannotations.OpenApiPaths;
import net.jacobpeterson.jet.openapiannotations.OpenApiResponse;
import net.jacobpeterson.jet.openapiannotations.OpenApiResponses;
import net.jacobpeterson.jet.openapiannotations.OpenApiSchema;
import net.jacobpeterson.jet.openapiannotations.OpenApiServer;
import net.jacobpeterson.jet.openapiannotations.schemaname.SchemaName;
import net.jacobpeterson.jet.server.JetServer;
import net.jacobpeterson.jet.server.handle.Handle;
import net.jacobpeterson.jet.server.route.simple.pathexact.PathExactRoute;
import net.jacobpeterson.jet.server.router.simple.ImmutableSimpleRouter;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;

import static net.jacobpeterson.jet.common.http.header.contenttype.ContentType.APPLICATION_JSON_STRING;
import static net.jacobpeterson.jet.common.http.method.Method.ToString.GET;
import static net.jacobpeterson.jet.common.http.status.Status.OK_200;
import static net.jacobpeterson.jet.openapiannotations.OpenApiParameter.ParameterLocation.QUERY;

@NullMarked
public class Server {

    @OpenApi(
            annotationGroupName = Web.PATH,
            info = @OpenApiInfo(
                    title = "Web",
                    version = "1.0.0"
            ),
            servers = @OpenApiServer(url = "http://localhost/" + Web.PATH)
    )
    public static final class Web {

        public static final String PATH = "web";

        private final @Getter AccountHandlers accountHandlers;

        public Web(final ImmutableSimpleRouter.Builder router) {
            accountHandlers = new AccountHandlers(router);
        }

        @SchemaName("Account")
        public static final class AccountHandlers {

            public AccountHandlers(final ImmutableSimpleRouter.Builder router) {
                router.addLast(PathExactRoute.builder()
                        .method(GetInfo.METHOD)
                        .path("/" + Web.PATH + GetInfo.PATH)
                        .build(), this::getInfo);
            }

            public static final String TAG_NAME = "account";

            public static final class GetInfo {

                public static final String METHOD = GET;
                public static final String PATH = "/" + TAG_NAME + "/info";
                public static final String QUERY_KEY_ID = "id";

                @Value @Builder
                public static class Success {

                    String name;
                    String email;
                    @Nullable String profilePictureUrl;
                }

                public enum FailReason {

                    NOT_LOGGED_IN,
                    INVALID_ID
                }

                @Value @Builder
                public static class Response {

                    @Nullable Success success;
                    @Nullable FailReason failReason;
                }
            }

            @OpenApi(annotationGroupName = PATH, paths = @OpenApiPaths(@OpenApiPathItem.MapEntry(
                    key = GetInfo.PATH, value = @OpenApiPathItem(methods = @OpenApiPathItem.MethodEntry(
                    key = GetInfo.METHOD, value = @OpenApiOperation(tags = TAG_NAME,
                    parameters = @OpenApiParameter(
                            name = GetInfo.QUERY_KEY_ID,
                            in = QUERY,
                            required = true,
                            schema = @OpenApiParameter.Schema(schema = @OpenApiSchema(fromClass = String.class))
                    ),
                    responses = @OpenApiResponses({@OpenApiResponse.MapEntry(
                            keyEnum = OK_200,
                            value = @OpenApiResponse(content = @OpenApiMediaType.MapEntry(
                                    key = APPLICATION_JSON_STRING,
                                    value = @OpenApiMediaType(schema = @OpenApiSchema(fromClass =
                                            GetInfo.Response.class))
                            ))
                    )})
            ))))))
            public void getInfo(final Handle handle) {
                if (<check_logged_in_logic>) {
                    handle.getResponse().responseJson(toJson(GetInfo.Response.builder()
                            .failReason(GetInfo.FailReason.NOT_LOGGED_IN)
                            .build()));
                    return;
                }
                final var id = handle.getRequest().getUrl().getQueryValue(GetInfo.QUERY_KEY_ID);
                if (<check_id_logic>) {
                    handle.getResponse().responseJson(toJson(GetInfo.Response.builder()
                            .failReason(GetInfo.FailReason.INVALID_ID)
                            .build()));
                    return;
                }
                <get_account_info_logic>
                handle.getResponse().responseJson(toJson(GetInfo.Response.builder()
                        .success(new GetInfo.Success(name, email, profilePictureUrl))
                        .build()));
            }
        }
    }

    static void main() {
        final var router = ImmutableSimpleRouter.builder();
        new Web(router);
        JetServer.builder().router(router.build()).build();
    }
}

OpenAPI Annotations Plugin

A code-first OpenAPI specification annotations processor Gradle plugin.

This Gradle plugin generates OpenAPI specifications from the OpenAPI annotations declared in your Java source code. Then you can use the OpenAPI Generator Gradle plugin to generate OpenAPI client libraries all within your Gradle build script. This creates an end-to-end solution for building APIs in a type-safe and single-source-of-truth manner, all with great developer experience. For example, you have a model class called GetInfo inside a class called AccountHandlers. The GetInfo class has the field @Nullable String firstName, but you want to change it to String firstName denoting that it is no longer nullable. If you're using a generated OpenAPI TypeScript client library, your TypeScript codebase will throw a compilation error for all the cases your frontend TypeScript code uses firstName as a nullable value, allowing you to catch type-safety bugs for APIs at compile-time instead of at runtime! This is how full-stack web development should have been all along!

Installation

For build.gradle.kts:

plugins {
    id("net.jacobpeterson.jet.openapiannotationsplugin") version "3.3.0"
}

For build.gradle:

plugins {
    id 'net.jacobpeterson.jet.openapiannotationsplugin' version "3.3.0"
}

There is no Maven plugin available at this time.

Guide

This Gradle plugin registers a task named jetOpenApiAnnotations and an extension also named jetOpenApiAnnotations with the following configurations:

Example Configuration (build.gradle.kts)

jetOpenApiAnnotations {
    schemaGeneratorUseGsonModule = true // Set to this `true` if you're using Gson
    schemaGeneratorUseJacksonModule = false // Set to this `true` if you're using Jackson
    // If you have a custom (de)serializer for `InetAddress` with Gson or Jackson, you can specify that every time
    // `InetAddress` is used in a model class for an OpenAPI schema, it should be treated as a string.
    schemaGeneratorSimpleTypeMappings.put("java.net.InetAddress", """{"type": "string"}""")
}

Why not an annotation processor?

The main reason is that annotation processors cannot load classes if the class is part of the source code currently being compiled, so OpenApiSchema.fromClass() would not be able to utilize the excellent victools/jsonschema-generator library and the class file would have to be manually inspected using TypeMirror. Additionally, annotation processors have various limitations, such as no support for @CompileClasspath for Gradle incremental builds.

Client

A simple, lightweight, modern, turnkey, Java web client library.

The Client module is a WIP and will be released soon! See issue #5.

Personal Note About AI

100% of this codebase was written by a human. 100% of this guide was written by a human. No AI slop is allowed here. I care deeply about good engineering and excellent code quality. AI does not. So, do I use AI? Of course! But it will stay inside my web browser as a chatbot where I'll ask it questions about specific problems, like a glorified StackOverflow. I remain in the driver's seat. It's not in my IDE where an AI autocomplete is constantly suggesting average buggy code, and certainly not where AI agents are mangling the codebase. Yes, AI autocompleted code can be reviewed, but I find myself accepting lower quality code instead of critically thinking through each line and typing it myself, which almost always yields higher quality code, but takes longer. LLMs fundamentally output the mean. I'm not saying that my code is always perfect and better than AI, but I am saying that too many engineers are being sold a lie that if they aren't constantly using AI, they will be left behind. Until there is a fundamental change in how LLMs work, this will remain my opinion on the state of AI coding. </rant>

Contributions are very welcome, but keep these remarks in mind when submitting pull requests.

Alternatives

Please note that the below list focuses on the weaknesses of the alternative projects compared to Jet's strengths. Jet obviously has shortcomings compared to these other projects. Personal note: the below list is highly opinionated as a result of my work as a full-stack software engineer, almost always using a Java backend.

  • Jetty - excellent web server and client library (which is why Jet uses it), but has a complicated setup and lacks exhaustive Javadocs and header models

  • Javalin - awesome library, but requires Kotlin dependency and lacks header models and exhaustive KDocs

  • Spring - enterprise-grade, but bloated, uses lots of runtime annotation magic, and enforces an opinionated codebase structure

  • Vert.x Web - reactive programming is no longer necessary with the advent of Virtual Threads

  • Apache Tomcat - strict legacy Jakarta Servlet specification adherence

  • Spark Java - project abandoned