feat: custom observation API, OTLP bundle, and OpenTelemetry log correlation
📢 Documentation Update
This README has been updated to reflect our new Docusaurus-based documentation site. For the most current documentation, please visit the official Apache Juneau website.
Note: The documentation is automatically updated and provides the most current project information.
Apache Juneau™ excels in the following scenarios:
<dependency> <groupId>org.apache.juneau</groupId> <artifactId>juneau-shaded-all</artifactId> <version>10.0.0</version> </dependency>
import org.apache.juneau.json.*; public class QuickStart { public static void main(String[] args) { // Create a simple POJO Person person = new Person("John", 30); // Serialize to JSON String json = Json.of(person); System.out.println(json); // Output: {"name":"John","age":30} } public static class Person { public String name; public int age; public Person(String name, int age) { this.name = name; this.age = age; } } }
// Parse JSON back to POJO Person parsed = Json.to(json, Person.class); System.out.println(parsed.name); // Output: John
import org.apache.juneau.rest.*; import org.apache.juneau.rest.servlet.*; @Rest( title="Hello World API", description="Simple REST API example" ) public class HelloWorldResource extends BasicRestServlet { @RestGet("/hello/{name}") public String sayHello(@Path String name) { return "Hello " + name + "!"; } @RestGet("/person") public Person getPerson() { return new Person("Jane", 25); } }
import org.apache.juneau.rest.mock.*; public class ApiTest { @Test public void testHello() throws Exception { String response = MockRestClient .create(HelloWorldResource.class) .json5() .build() .get("/hello/World") .run() .assertStatus().is(200) .getContent().asString(); assertEquals("Hello World!", response); } }
That's it! You now have:
juneau-config for INI-style configsjuneau-rest-server-springbootimport org.apache.juneau.xml.*; // Serialize to XML String xml = Xml.of(person); System.out.println(xml); // Output: <object><name>John</name><age>30</age></object> // Parse XML back to POJO Person parsed = Xml.to(xml, Person.class);
import org.apache.juneau.html.*; // Serialize to HTML table String html = Html.of(person); System.out.println(html); // Output: <table><tr><th>name</th><td>John</td></tr><tr><th>age</th><td>30</td></tr></table>
import org.apache.juneau.config.*; // Create configuration Config config = Config.create() .set("database.host", "localhost") .set("database.port", 5432) .set("features.enabled", true) .build(); // Read configuration String host = config.get("database.host"); int port = config.get("database.port", Integer.class); boolean enabled = config.get("features.enabled", Boolean.class);
import org.apache.juneau.rest.client.*; import org.apache.juneau.http.*; // Define REST interface @Remote("http://api.example.com") public interface UserService { @Get("/users/{id}") User getUser(@Path String id); @Post("/users") User createUser(@Body User user); } // Use as regular Java interface UserService service = RestClient.create().build().getRemote(UserService.class); User user = service.getUser("123");
import org.apache.juneau.rest.mock.*; // Test without starting a server @Test public void testUserAPI() throws Exception { String response = MockRestClient .create(UserResource.class) .json5() .build() .get("/users/123") .run() .assertStatus().is(200) .getContent().asString(); assertThat(response).contains("John"); }
import org.apache.juneau.microservice.*; // Create microservice Microservice microservice = Microservice.create() .servlet(UserResource.class) .port(8080) .build(); // Start server microservice.start();
Apache Juneau™ is a single cohesive Java ecosystem consisting of the following parts:
Questions via email to dev@juneau.apache.org are always welcome.
Juneau is packed with features that may not be obvious at first. Users are encouraged to ask for code reviews by providing links to specific source files such as through GitHub. Not only can we help you with feedback, but it helps us understand usage patterns to further improve the product.
The documentation site is a Docusaurus 3 project. Builds and deploys are driven by local Python scripts under scripts/ — there is no longer a push-triggered GitHub Action; publishes happen when a human runs the script.
Prerequisites:
~/jdk/openjdk_17.0.14.0.101_17.57.18_aarch64/bin/java on this workstation)site build)master checkout of the Juneau Java repository at ../master/ (scripts/build-docs.py runs Maven site there to harvest the javadocs)Dev server (hot reload, no deploy):
python3 scripts/start-docusaurus.py # serves http://localhost:3000
The script kills any process on port 3000, clears the Docusaurus cache, runs npm install if needed, and starts npm start. Edit pages/ and the page reloads automatically.
Full local build (Maven site + Docusaurus + link checks):
python3 scripts/build-docs.py # full pipeline python3 scripts/build-docs.py --dry-run # print everything it would do, run nothing python3 scripts/build-docs.py --verbose # full per-stage banners python3 scripts/build-docs.py --skip-maven # Docusaurus only (no Java work) python3 scripts/build-docs.py --skip-npm # Maven site only python3 scripts/build-docs.py --staging # set SITE_URL to juneau.staged.apache.org
Output lands in build/. On a successful full build the script prints a publish reminder.
Local production preview (no deploy, just docusaurus serve over the prod build):
npm run build && npm run serve
Deploy to staging (https://juneau.staged.apache.org):
git config --get user.email # must be jamesbognar@apache.org python3 scripts/release-docs-stage.py # (use --no-push to rehearse without pushing)
The script verifies the git identity, runs the staged build, clones (or updates) a sibling ../asf-staging/ checkout, copies the build output in, commits, and force-pushes asf-staging to origin. Open http://juneau.staged.apache.org to verify before promoting.
Promote staging to production (https://juneau.apache.org):
git config --get user.email # must be jamesbognar@apache.org python3 scripts/release-docs.py # (use --no-push to rehearse)
This promotes whatever is currently on origin/asf-staging to asf-site. Only run after eyeballing the staged site.
AI-driven flows: the Cursor / Claude @juneau-docs-workflow skill provides natural-phrase routing for all of the above (“start docs locally”, “deploy docs to stage”, “deploy docs to prod”, “add to release notes”, etc.).
PR smoke check: every pull request that touches Markdown, config, or scripts automatically runs python3 scripts/build-docs.py --skip-maven via .github/workflows/docs-smoke.yml. If your PR fails CI, run the same command locally to reproduce — the Docusaurus error output points directly to the broken file and line.
Building requires: