# JBang ## Posts ### [JBang IntelliJ Plugin — Rewritten from Scratch](/learn/jbang-intellij-plugin-rewritten-from-scratch/) The JBang IntelliJ plugin has been completely rewritten. Version 0.100.0 is not an incremental update — it is a ground-up rewrite that makes JBang scripts proper first-class citizens inside IntelliJ IDEA. What changed and why The old plugin (≤ 0.26) worked by mutating IntelliJ modules to try and get intellij to fit to JBang. That approach was fragile — it used old API that Jetbrains marketplace kept blocking the release for. It also conflicted with Gradle and Maven projects, and could not handle multiple scripts with different classpaths in the same directory. The new plugin takes a fundamentally different approach: it uses jbang info tools as its single source of truth like how the VSCode plugin does but goes a step further and overlays dependencies as synthetic libraries. Hence making IntelliJ aware of JBang scripts without ever mutating the project model. This means that you can have a Gradle or Maven project and still use JBang scripts in the same folder without any conflicts. Total freedom! Per-script classpath isolation Each JBang root script now keeps its own isolated classpath, declared sources, and requested Java version. If you have three scripts in the same directory with different //DEPS, each one resolves independently. You can even have multiple roots that reuses the same source files. When multiple roots you can switch between which is used for classpath resolving. Total freedom! Dependencies show up under External Libraries as clearly labeled JBang entries — you can navigate from a library JAR back to the root script that owns it (press kbd:Enter) Other big enhancements include that Run and Debug are now fully supported, with JDWP auto-attach for debugging. Run JBang also now use IntelliJ’s terminal so you can write and debug TUI applications without leaving the IDE. Finally! What got fixed This rewrite addressed a long list of issues and feature requests that had accumulated over the years: #153 — Poor experience with multiple scripts in one directory → multi-root isolation #112 — Sync dependencies between JBang and IDEA .iml file → overlay-based, no .iml mutation #111 — Debug button disabled → full Run + Debug support with JDWP auto-attach #136 — Cannot add environment variables to run config → expanded run configuration #128 — Support Kotlin → Kotlin, Groovy, JShell, and .jbang all supported #139 — JDK version overwritten when //JAVA is omitted → standalone SDK handling fixed #135 — Code completion proposes wrong version numbers → rewritten Maven coordinate completion with local/remote/snapshot distinction #147 — Failed to resolve DEPS with global JVM options → CLI invocation rewritten #143, #134, #96 — Various IntelliJ compatibility exceptions → clean platform API usage #151 — Blurry icon → all icons replaced with SVGs #124 — WSL support → now supported #102 — Improve the JBang experience in IntelliJ → well, that was the whole point #160 — Run config breaks on file rename/move → fixed #161 — Non-JBang files checked for JBang errors → fixed, directives only matched at column 0 New capabilities Beyond fixing what was broken, the rewrite adds things that were never possible before: //SOURCES and //FILES navigation — Ctrl/Cmd-click jumps to declared sources and resources, with path completion as you type. Catalog support — JSON Schema for jbang-catalog.json, plus script-ref completion and navigation. Exact diagnostics — unknown directives, malformed coordinates, duplicate //DEPS, unresolved dependencies, and missing resources highlighted on the exact failing token. Terminal execution — run scripts in IntelliJ’s terminal with Windows PTY fallback. CLI-backed templates — New → JBang Script loads templates directly from jbang template list. Contextual feature tips — the plugin walks you through its capabilities as you use them. What got removed The rewrite also deleted code that was never working well or pulling its weight: Module builder/wizard — just open a folder. Gradle dependency mirroring — the config file was empty, it never actually worked. DependencyModifier — always returned false. Custom tool window with dependency list — replaced by status bar widget and External Libraries. Live templates — replaced by CLI-backed templates that stay in sync with JBang itself. Try it The plugin has been submitted to the JetBrains Marketplace but approval can take a few days. You don’t have to wait — there are two ways to get it now: Option 1: Add the EAP update channel (recommended) This gives you automatic updates for every new build, including pre-release fixes: In IntelliJ: Settings → Plugins → ⚙ → Manage Plugin Repositories… Add: https://plugins.jetbrains.com/plugins/eap/18257 Click OK, then search for "JBang" in the Marketplace tab and install or update. The EAP channel receives every successful build from main, so you always get the latest. Option 2: Install from disk Download jbang-idea-plugin-0.100.0.zip from the GitHub release. In IntelliJ: Settings → Plugins → ⚙ → Install Plugin from Disk… → select the zip. Restart. Once JetBrains approves the update it will show up in the stable Marketplace channel as usual. Please try it and experiment with proper IDE integration for your JBang scripts — and let us know how it works! Feedback, bug reports, and feature requests are welcome at github.com/jbangdev/jbang-idea/issues. ### [Somehow, JBang Just Passed 3 Million Downloads](/learn/somehow-jbang-just-passed-3-million-downloads/) JBang just passed 3 million binary downloads. 🎉 I honestly never expected that number when I started tinkering with making Java easier to run back in 2019. But the fun part isn’t the milestone itself. It’s what the numbers behind it reveal. Because somewhere along the way, JBang seems to have grown into something quite different from what I originally set out to build. First: thank you Before I dive into numbers, thank you. To everyone who has used JBang, reported a bug, contributed code, created a catalog, built a tool on top of it, mentioned it to a colleague, or just typed jbang into a terminal one day. A special thank you to Tako Schotanus. JBang would not be what it is today without his contributions. Open source projects become useful because people find uses for them their creators never anticipated. Three million downloads is your milestone too. What does "3 million" actually mean? The number comes from the GitHub Releases API. It counts downloads of JBang’s actual distributable binary artifacts. Our download statistics page deliberately separates out version.txt (which JBang uses for update checks) and also excludes checksums and signatures. This is not a counter inflated by millions of automatic pings. When we crossed the milestone, the counter showed: 3,000,330 JBang binary downloads Now, that is not the same as three million unique users. People reinstall. CI systems pull binaries. Machines get replaced. But it does represent real downloads of JBang distributions, not background telemetry. And people keeps downloading it JBang started in 2019. Seven years later, it is seeing roughly 1,000 binary downloads every day based on the recent period around the milestone. That works out to approximately: 30,000 downloads per month 380,000 downloads per year if the recent pace holds The interesting part isn’t just that JBang eventually reached three million downloads. It is that people keep discovering and installing it, every day, seven years in. I’d love to tell you exactly when we crossed one million and two million, and draw a nice growth curve. But GitHub only exposes the current cumulative download count per release asset. No historical snapshots. I’m not going to pretend we have data we don’t have. We’re starting to record proper snapshots now, so the next milestone can tell that story. Downloads are only part of the story JBang also does an async version check, at most once a day per installation, unless it is disabled or finishes too fast for the check to fire. The anonymous, aggregated data from those checks is published at jbang.dev/usage. Over the rolling 90-day window: hundreds of thousands of version checks from more than 130 countries. That is still not a user count. Some installations check in often, some JBang usage is never observed at all. Proxies, short-lived executions, offline environments and disabled checks all create blind spots. But it paints a picture: there is a large, globally distributed population of JBang installations that keeps showing up. The geographical spread genuinely inspires me. The United States is the largest single country in the leaderboard, but it’s only around 16% of what we see. The large majority of observed usage is everywhere else. JBang is very much a global tool. JBang installations apparently live for a long time One of the most interesting things in the usage leaderboard is how many old JBang versions still show up in today’s telemetry. It’s not just people upgrading to last week’s release. Versions from years ago are still checking in. That tells me JBang has found its way into developer machines, scripts, CI pipelines, internal tooling, workshops, automation…​ and then just stayed there, doing its job. That looks less like something people try once. It looks more like infrastructure. It spans almost the entire modern Java world The same data shows JBang running on everything from Java 8 to current releases to early-access builds. And across dozens of JDK vendors: Eclipse Adoptium, Oracle, Ubuntu, Amazon, Homebrew, Azul, Red Hat, GraalVM, Microsoft, and plenty more. That might be the part I’m most proud of. JBang was never meant to create or cement another isolated Java ecosystem. The goal was the opposite: work with your Java, on your machine, regardless of where either came from. The numbers say that’s actually happening. JBang started as "Java scripting" The original idea was pretty simple: Why should running a small Java program require creating an entire project? I wanted this to feel completely normal: jbang hello.java From there came dependency declarations, JDK provisioning, catalogs, app installation, Maven coordinates, aliases, and a lot of other things. And somewhere along the way, what JBang actually does shifted. Today a command like: jbang some-tool@some-catalog can mean: Find the tool. Find the right Java. Download the required dependencies. Resolve the application. Cache what can be cached. Run it. Make the same idea work across macOS, Linux and Windows. The person running the command often does not care about any of that. They just want to run the app; and JBang made that easy. Maybe JBang is really a Java distribution layer I still like the "Java scripting" idea. That’s still an important part of JBang. But looking at how people actually use it today, I think another description has become just as accurate: JBang is a zero-friction way to run and distribute Java tools. A growing number of projects use JBang not because their users want to become JBang users. They use it because their users want to run their tool, and JBang gets the rest out of the way. No project scaffolding. Possibly no Java installation beforehand. No manual dependency setup. One command, then get on with whatever you actually wanted to do. I didn’t fully anticipate that role back in 2019. But I think it may now be one of the most important things JBang brings. Three million, and still counting So yes: 🎉 JBang has passed three million binary downloads. That is worth celebrating. But the more exciting part is what’s behind that number: thousands of new downloads every week active installations appearing around the world usage across almost every corner of the Java ecosystem old installations that keep quietly doing their jobs tools using JBang as their own execution and distribution mechanism A weird little experiment to make Java easier to run has somehow become a piece of Java infrastructure. That is pretty cool. Thank you to everyone who helped make it happen. Onwards to the next three million. 🚀 Explore the numbers The data used in this post is public and continues to change: JBang download statistics Recent JBang usage JBang usage leaderboard Download counts come from GitHub Release asset counters. Usage data is anonymous, aggregated version-check telemetry. Treat it as an interesting signal, not a precise count of users. ### [From Picocli to Æsh: How Porting JBang's CLI Made Everything Better](/learn/from-picocli-to-sh-how-porting-jbang-s-cli-made-everything-better/) JBang 0.139.2 is out and it ships the biggest internal change in JBang’s history: we replaced picocli with Æsh for all CLI parsing. 5.5× faster native startup, smart tab-completion, a new TUI for dependency search, and (the part I didn’t expect) it made Æsh itself way better in the process. What started as a framework swap turned into an open-source feedback loop that improved everything it touched. Let me tell you how. Picocli set the bar First, credit where it’s due. Picocli by Remko Popma is the best Java CLI framework out there. We’ve used it since JBang’s first commit in 2020 and it served us well: annotation-driven commands, rich help output, nested subcommands, shell completions, you name it. With 5,400+ GitHub stars and a decade of active development, picocli is what made JBang’s CLI experience good in the first place. So why change? The speed problem The short answer: reflection. Picocli discovers commands and options at runtime using reflection, and that has a cost, especially under GraalVM native image. We measured JBang’s startup at 33ms in native and 228ms on JDK 25 with picocli. That’s fine for most tools. But JBang is something people run as casually as python myscript.py, and at that level the difference between "fine" and "instant" matters. We’ve had native-image builds of JBang for a while, but could never seriously offer them as the default. 33ms sounds fast until you’re doing tab-completion, where the CLI runs on every keypress. At that speed, you feel the lag. We needed a framework that could do what picocli does, without the runtime reflection. And that’s where Ståle Pedersen enters the picture. Enter Ståle and Æsh Ståle Pedersen is the creator of Æsh (Another Extendable SHell), a Java CLI library that’s been around since 2012, roughly as long as picocli itself. Ståle leads the IBM Runtimes Performance Team from Arendal, Norway, and he knows a thing or two about making Java fast. Æsh’s trick: it uses a compile-time annotation processor to generate all the command metadata. The generated code is a switch statement that maps options to fields. No reflection at startup, no classpath scanning. On April 28th, Ståle opened PR #2453: "feat: replace picocli with aesh for CLI parsing." 8,700 lines added, 12,400 removed, across 100 files. A full rewrite of JBang’s CLI layer. The numbers: Mode Picocli Æsh Speedup Native image 33ms 6ms 5.5× JDK 25 228ms 109ms 2.1× JDK 11 269ms 149ms 1.8× That’s the difference between "ok, I guess that’s Java" and "wait, is this really Java?" 278 commits in 8 weeks Porting JBang wasn’t just swapping annotations. JBang uses a lot of picocli features: mutually exclusive options, negatable flags like --[no-]verbose, custom type converters, dynamic completions, help sections, the works. Ståle didn’t say "sorry, Æsh doesn’t support that." He went and built it. In real time. While the PR was open. Between April 20th and June 18th, Ståle made 278 commits to Æsh and pushed 16 releases (3.6 through 3.15.1). Nine more releases of aesh-readline. Features he built because JBang needed them: fallbackValue for three-state option semantics (picocli parity) DefaultValueProvider with ${env:…​} and ${sys:…​} resolution exclusiveWith for mutually exclusive options HelpSectionProvider for custom help sections Negatable options rendering as --[no-]name ANSI-colored help output Synopsis wrapping at terminal width Shell completion generators for bash, zsh, fish, and PowerShell Documentation generators for AsciiDoc and Markdown GraalVM native-image configuration generation A BOM module for version management Every time JBang found something missing, Ståle had it fixed, often the same day. This wasn’t a port anymore, it was a co-evolution. As Ståle put it on Zulip: "I’m glad we get to 'harden' aesh so quickly :)" I can’t overstate how impressive this was. Open source is often about finding a project that already does 90% of what you need, and then working around the missing 10%. Ståle didn’t just fill in the gaps, he built the whole bridge while we were crossing it. The bug too fast to see My favorite moment from the migration. After merging the Æsh PR, JBang started hanging after certain commands. We tracked it down to a non-daemon thread doing a version check. It had always been there, but picocli was slow enough that it never materialized. Æsh was so fast the main thread finished before the version check could complete, and the JVM hung waiting for the non-daemon thread to exit. We went from 33ms startup to discovering bugs because 6ms wasn’t giving other threads enough time to start. I’ll take that trade. "Lets see how many things break" On June 3rd, we merged the PR. I posted on Zulip: "is now merged, lets see how many things break :)" Turns out, not much. And ironically that’s thanks to picocli. Over the years we’d built a solid integration test suite around picocli’s behavior. That same test suite is what gave us confidence the Æsh port was correct. The tests didn’t care which framework generated the CLI, they just verified the behavior. We did have some issues. Tako found that primitive boolean fields behaved differently, especially with jbang config overrides. Ståle had aesh fixes released within a day, even hours. The feedback loop was tight. The ecosystem gets supercharged The benefits of this migration didn’t just stay in JBang. Ståle’s work on Æsh made it a much more compelling framework for everyone else. I know we will see more adoption of Æsh in the Java ecosystem, and that will in turn make it even better. It’s a virtuous cycle of improvement. We have more things to come building on top of what Æsh enables, and I’m excited to see where it goes. What you get in 0.139.2 Native binaries (early access) We now publish GraalVM native-image builds for Linux, macOS, and Windows. Near-instant startup, no JVM required: export JBANG_USE_NATIVE=true curl -Ls https://sh.jbang.dev | bash -s - myapp.java The JVM fallback is always there if native doesn’t work on your platform. Smart tab-completion With Æsh fast enough to run on every keypress, we can finally do useful things with completions: Local files filtered to JBang-supported extensions (.java, .jsh, .kt, .groovy, .md) Alias names from catalogs (type @<TAB> to browse) Maven GAV completion from your local ~/.m2/repository GitHub URL navigation, completing files and directories in GitHub repos via API This works in bash, zsh, and fish today. PowerShell is coming. TamboUI dependency search The jbang deps search TUI got a full rewrite from JLine to TamboUI (also by Ståle). Fuzzy match highlighting, async search with debounce, a version picker side pane, and a party trick: paste a Java import statement or compiler error and it auto-searches Maven Central for the right dependency. Thank you This migration wouldn’t have happened without a lot of people. Ståle Pedersen created Æsh, opened the PR, and then made a tons of commits making it ready for real-world use. That kind of responsiveness is what open source makes fun. Tako Schotanus, JBang co-maintainer, found the subtle regressions and Werner Fouche and Juan Antonio Breña Moral helped with additional testing. M. helped Remko Popma built picocli, and our test suite built on picocli is what made this migration safe. Try it # Install or update JBang curl -Ls https://sh.jbang.dev | bash -s - app setup # Try native mode export JBANG_USE_NATIVE=true # Install completions jbang completion install # Try the new dependency search TUI jbang deps search If something broke or feels different from before, please open an issue. We have a solid test suite but real-world usage always finds things tests don’t. The full changelog is in the 0.139.2 release notes. Have fun! ### [Java in Places You Do Not Expect It](/learn/java-in-places-you-do-not-expect-it/) JBang is about making Java something you can edit, build, run, deploy and share with minimum ceremony and optimally zero friction. This is a work in progress, and this week I feel we got a big step closer to the ultimate goal. Best way to show this? Just try it yourself - it takes 5 seconds. Try Java in your browser Click this link → Run Java in your browser - wait a moment and you have a running Java environment, showing Hibernate example in your browser. No JDK or IDE to install, no account, just works. Or scroll down and click "Activate" below to run Java code right here in this blog post: { requestKernel: true, persistKernel: false, useJupyterLite: false, useBinder: true, binderOptions: { repo: "jupyter-java/jupyter-java-binder", ref: "jbang", binderUrl: 'https://mybinder.org' }, kernelOptions: { kernelName: "jbang", }, codeMirrorConfig: { } } //DEPS io.jeamlit:jeamlit:0.46.0 //DEPS ch.qos.logback:logback-classic:1.5.19 import org.icepear.echarts.Bar; import io.jeamlit.core.Nb; import io.jeamlit.core.Jt; // only need to run this once - jeamlit shared CSS and web components display(Nb.getInitHtml(), "text/html"); // some chart Bar bar = new Bar() .setLegend() .setTooltip("item") .addXAxis(new String[] { "Matcha Latte", "Milk Tea", "Cheese Cocoa", "Walnut Brownie" }) .addYAxis() .addSeries("2015", new Number[] { 43.3, 83.1, 86.4, 72.4 }) .addSeries("2016", new Number[] { 85.8, 73.4, 65.2, 53.9 }) .addSeries("2017", new Number[] { 93.7, 55.1, 82.5, 39.1 }); display(Nb.render(Jt.echarts(bar).build()), "text/html"); That is real Java. With real Maven dependencies. Output rendered in your browser with no additional install. For those curious - above is using Jeamlit to render the chart in your browser. It should look something like this: Jeamlit is a new library similar to Streamlit for Python, not done by us but inspired by JBang that allows building interactive web apps in Java with zero friction - do check it out! What was made I’ve been wanting to use //DEPS in Jupyter Notebooks for a long time, but it has been a pain to set up, so I talked with maintainers of JJava and Rapaio kernels and explored options. Each has their own way of dealing with dependencies - and I prefer to minimize the friction thus I created a JJava derived kernel that supports //DEPS directives in notebooks. And in doing so I discovered that we could use Python’s existing infrastructure (Jupyter Notebooks, MyBinder and Thebe) to go some steps further. There is a shockingly small amount of code to make this all work - and its in its infancy still; there is a lot more one can do with this - and I’m hoping you all will help make it better to make Java something you can share and use with zero friction. Here’s what we’ve built: Created a custom JBang Jupyter kernel that supports //DEPS directives in notebooks, based on JJava kernel Built a Binder environment for running Java kernels in your browser with no install Developed jbang-jupyter-runner extension to run .java/.jsh files directly in Jupyter Lab Put it all together and you have a way to run Java code in your browser with no additional install. Note: This is not just for browser-based notebooks - it also works in IntelliJ, VSCode, Cursor and other IDEs that support Jupyter Notebooks. How to Try It The fastest way is our new jbang.dev/try page. In your browser (no install): Click this Hibernate example Try custom code Build your own shareable links for your repositories Locally (with Jupyter): jbang install-kernel@jupyter-java --java 25 --enable-preview jbang Works in IntelliJ, VSCode, Cursor, and any IDE with Jupyter support! If not - let us know! What This Unlocks Instant workshops — A browser tab replaces a 45-minute install session. Runnable documentation — Stop making readers imagine the output. Let them press Run. Shareable bug reproductions — "Can you reproduce this?" becomes a link, not a setup guide. API exploration — Test a library with //DEPS, not pom.xml. This is not about data science or competing with Python. This is about making Java something you can share with zero friction — all in line with JBang’s mission. Try It Now Ready to experience zero-friction Java? Get started: Try the interactive demo right now - multiple samples available Click this Hibernate example to see it in action Create custom shareable links for your own projects Install locally: jbang install-kernel@jupyter-java --java 25 --enable-preview jbang This is still new and evolving - we have more ideas and plans for this, so stay tuned! This is about making Java something you can share with zero friction — all in line with JBang’s mission. Enjoy! ### [Introducing jbang-fmt](/learn/introducing-jbang-fmt/) I really did not want to do this, but after I and others tried to convince google-java-format one too many times to not mess with JBang directives like //DEPS or the ///usr/bin/env lines, I finally gave up and made my own. While I’m a fan of tools like Spotless, their command-line usage is still too cumbersome. This prompted me to create an improved solution called jbang-fmt, in the hope that its key features will be adopted by competing programs and ultimately render itself obsolete So here it is: https://github.com/jbangdev/jbang-fmt I would have called it java-fmt as it is not tied to JBang - you can use it as-is with any Java code; but I don’t like visits from lawyers. Why jbang-fmt? Simple: One command to format any Java file or directory Fast: Built for command-line usage and using virtual threads Flexible: All the options you need (but probably shouldn’t use) JBang-aware: Respects //DEPS and ///usr/bin/env directives It is a simple tool that formats Java code using Eclipse Formatter![1] but by default does not mess with JBang directives like //DEPS or the ///usr/bin/env lines. In its simplest form you just do: jbang-fmt . And it will format all the Java files recursively in the current directory. If you want to format specific set of files and directories you can do: jbang-fmt myscript.java src/main That’s it - you don’t need to read more, just install it and use it. Installation jbang app install jbang-fmt@jbangdev One more thing…​ Since I made this in like 30 minutes as I was being nerd sniped I couldn’t resist adding a few more bell and whistles. Before I show them - please know - that I really highly recommend you use one format in your project and in many ways I fully agree with google-java-format faq on locking down the formatting rules. That said - the world is a messy place and we all have different needs - so I like to make formatting easy to do and enable easy experimentation - so I added all the ways I can think of a simple formatting tool could be useful. --check This is a really simple flag that will check if the code is formatted without making any changes. jbang-fmt --check . If the code is not formatted it will exit with a non-zero code. Useful in CI pipelines to fail the build if the code is not formatted and as git pre-commit hooks - see Git Integration for an example of the latter. Maven and Gradle integration You can use it from Maven and Gradle using their jbang support - no additional plugins needed. See Maven and Gradle in documentation for more details. Shortcuts for common "debates" Tabs vs spaces? Use --indent-with=tab or --indent-with=space Big vs small monitor? Use --line-length=120 or --line-length=40 Java version nostalgia? Use --java-version=4 for the good old days or --java-version=25 for living on the edge Compact docs? Use --indent-size=2 etc. …​and if you want full control you can use -Skey=val to override any Eclipse formatter property. But again, remember I told you to just use one format - above is not meant to be used unless you are ready for war. In addition it also supports using @-files in case you ignored my advice and want to go crazy on shared formatting. Conclusion So - what are you waiting for? Install it and start formatting your code today! And if you have any feedback, suggestions, or want to contribute, please open an issue on GitHub! 1. I wanted to offer both Google Java Format and Eclipse but Google Java Format even on API level seems to have no consistent ability to ignore lines. ### [Manifest Tool](/learn/manifest-tool/) Say hello to mf@jbangdev — the new, delightfully simple way to peek inside any JAR’s manifest! Ever wondered what secrets lurk in META-INF/MANIFEST.MF? Tired of squinting at cryptic key-value pairs or wrangling with unzip just to see what’s inside? Now you can summon manifest magic with a single command: mf `jbang info jar com.h2database:h2:2.2.224` This will use jbang to fetch the jar for h2 and then use mf to print the manifest. Manifest-Version: 1.0 Implementation-Title: H2 Database Engine Implementation-URL: https://h2database.com Implementation-Version: 2.2.224 Build-Jdk: 1.8 Created-By: 1.8.0_281-b09 (Oracle Corporation) Main-Class: org.h2.tools.Console Automatic-Module-Name: com.h2database Bundle-Activator: org.h2.util.DbDriverActivator Bundle-ManifestVersion: 2 Bundle-Name: H2 Database Engine Bundle-SymbolicName: com.h2database Bundle-Vendor: H2 Group Bundle-Version: 2.2.224 Bundle-License: https://h2database.com/html/license.html Bundle-Category: jdbc Multi-Release: true Import-Package: javax.crypto,javax.crypto.spec,javax.management,javax.naming;resolution:=optional,javax.naming.directory;resolution:=optional,.. Export-Package: org.h2;version="2.2.224",org.h2.api;version="2.2.224",org.h2.constant;version="2.2.224",org.h2.fulltext;version="2.2.224"... Provide-Capability: osgi.service;objectClass:List<String>=org.osgi.service.jdbc.DataSourceFactory Premain-Class: org.h2.util.Profiler Installation You can run it directly using jbang mf@jbangdev or install it using jbang install mf@jbangdev to get it as a standalone tool. Structured output You’ll notice keys like Import-Package and Export-Package are very long and hard to read. mf allows enabling structured output (-s or --structured) that supports certain well-known keys and when enabled will print the values as lists or maps. mf `jbang info jar com.h2database:h2:2.2.224` -s Manifest-Version: 1.0 Implementation-Title: H2 Database Engine ... Import-Package: javax.crypto: true javax.crypto.spec: true javax.management: true javax.naming: resolution: optional javax.naming.directory: resolution: optional javax.naming.spi: resolution: optional ... Export-Package: org.h2: version: 2.2.224 org.h2.api: version: 2.2.224 org.h2.constant: version: 2.2.224 org.h2.fulltext: version: 2.2.224 ... Provide-Capability: osgi.service;objectClass:List<String>=org.osgi.service.jdbc.DataSourceFactory Premain-Class: org.h2.util.Profiler Still long, but much easier to read. Filtering mf supports filtering the manifest keys using a regex. In its simplest form it lets you substring on keys mf `jbang info jar com.h2database:h2:2.2.224` -s version Manifest-Version: 1.0 Implementation-Version: 2.2.224 ... but you can also use a regex to filter the keys. mf `jbang info jar com.h2database:h2:2.2.224` "bundle|version" Manifest-Version: 1.0 Implementation-Version: 2.2.224 Bundle-Activator: org.h2.util.DbDriverActivator Bundle-ManifestVersion: 2 Bundle-Name: H2 Database Engine Bundle-SymbolicName: com.h2database Bundle-Vendor: H2 Group Bundle-Version: 2.2.224 Bundle-License: https://h2database.com/html/license.html Bundle-Category: jdbc JSON & YAML Finally, mf supports JSON(--json) and YAML(--yaml) output. mf `jbang info jar com.h2database:h2:2.2.224` --json and they can be combined with the other options: mf `jbang info jar com.h2database:h2:2.2.224` --yaml -s "version|package" Manifest-Version: "1.0" Implementation-Version: "2.2.224" Bundle-ManifestVersion: "2" Bundle-Version: "2.2.224" Import-Package: javax.crypto: true javax.crypto.spec: true javax.management: true javax.naming: resolution: "optional" javax.naming.directory: resolution: "optional" javax.naming.spi: resolution: "optional" javax.net: true javax.net.ssl: true javax.script: resolution: "optional" ... Export-Package: org.h2: version: "2.2.224" org.h2.api: version: "2.2.224" org.h2.constant: version: "2.2.224" org.h2.fulltext: version: "2.2.224" org.h2.jdbc: version: "2.2.224" ... ### [JBang moving into CommonHaus Foundation](/learn/jbang-moving-into-commonhaus-foundation/) JBang and its associated projects is joining as a founding project of the CommonHaus foundation. CommonHaus is a brand-new foundation which focus is on providing an independent home for projects like JBang that are funded by independents and with room for project indivduality. Other founding projects are Hibernate, Jackson, OpenRewrite, JReleaser & Morphia. Why JBang in a foundation? The decision to include JBang in a foundation stems from a desire to ensure its long-term sustainability and growth. Being part of a foundation, especially CommonHaus, provides JBang with a stable, independent home that supports its development and promotes collaboration. This move also opens up new opportunities for funding and contributions, further securing the project’s future and fostering an environment where JBang can thrive alongside other like minded open-source projects. Why CommonHaus? The guiding principles of CommonHaus summarizes it well: Honor project and community identity Offer guidance and support instead of imposing mandates Maintain transparency in all of our actions Encourage long-term thinking for sustained project impact These are important things for JBang - having a place to be hosted for ensuring continuity while still allowing individuality. What does it mean for users? JBang will stay JBang. If you as a user notice it has moved, I will be surprised. Maybe the about text of the CLI will change; that is about it. What does it mean for contributors? The exact onboarding implication is still being defined, but it is expected to have minimal impact as we do most of the recommended things anyways. What’s next? Try out JBang if you haven’t already. If you see something that could need improvement or have a question, join the development on GitHub, open issues and discussions, and let’s help make Java truly available to anyone. Have fun! ### [Introducing jbanghub](/learn/introducing-jbanghub/) Today we are introducing JBangHub! JBang always allowed everyone easily make their own java/kotlin/groovy/etc. command line tools available in a jbang catalog. All it takes is that you make a jbang-catalog in your project, organization or website. This is what makes things like jbang jreleaser@jreleaser and jbang minecraft-server@microsoft possible with minimal effort. These can then be called as-is or even installed using jbang app install making them available in your PATH directly. Many already added lots of catalogs and they are visible on the JBang AppStore, readily available to be used from anywhere with JBang available. This is anywhere you can run java including from javascript and python - yes, even Jupyter Notebooks can access Java with JBang. To make it even easier and make it possible for anyone to make their favourite tools available under a nice and simple name we are introducing JBangHub, a GitHub organization where we invite everyone to contribute scripts and tools they would like to see easily available. JBangHub is there to let anyone help any project become more easily accessible to the Java community. Automatic dependency updates We made a jbang-catalog template available that setups of a jbang-catalog with automatic renovate bot based updates. All repos in JBangHub uses this to ensure any Maven referenced artifacts will get updates, but anyone can use this in their own jbang-catalog outside JBangHub too. Just start it using jbang init -t jbang-catalog <reponame> and add aliases to your jbang-catalog. Default Catalog The latest release (0.116) of JBang adds jbanghub as a default jbang catalog so when you do jbang catalog list you get something like: This list is not intended to be every jbang-catalog on the planet - but more a teaser on what is available out there. We encourage and accept edits/updates to that list at https://github.com/jbanghub/jbang-catalog. Help expand Think something is missing - come contribute your favourite tool at JBangHub. Have fun! ### [jbang-python: Java in your Python](/learn/jbang-python-java-in-your-python/) Happy to announce jbang-python - a pypi package which brings your Java to your Python project or notebook. jbang-python supports running any kind java code that JBang supports. Wether that is a script from the AppStore, a Maven GAV, a jar or simply a java file stored in your project. Example In your requirements.txt add: jbang-python or install it using pip install jbang Then in your python code you can do: import jbang output = jbang.exec('--java 21', 'properties@jbangdev', 'java').stdout print(output) output = jbang.exec('hello.java').stdout print(output) With a hello.java that contains: //DEPS com.github.lalyos:jfiglet:0.0.8 import com.github.lalyos.jfiglet.FigletFont; class hello { public static void main(String... args) throws Exception { System.out.println(FigletFont.convertOneLine( "Hello " + ((args.length>0)?args[0]:"jbang"))); ;; } } When you run this it will automatically install JBang and Java if necessary and then run the requested alias and java code. Besides using it in your python code locally you can also use it from Jupyter Notebooks. The jbang-python package is available on pypi and the source code is available on https://github.com/jbangdev/jbang-python. Have fun! ### [jbang-npm: Java Script in your JavaScript](/learn/jbang-npm-java-script-in-your-javascript/) Today we are announcing jbang-npm a npm package which brings you Java Script to your JavaScript project. jbang-npm joins jbang-maven and jbang-gradle as tools that brings the power of Java Script to your favourite build setup. jbang-npm supports running any kind java code that JBang supports. Whether that is a script from the AppStore, a Maven GAV, a jar or simply a java file stored in your project. Example Given you have a script name test.js: #! /usr/bin/env node const jbang = require('@jbangdev/jbang'); jbang.exec('--java 18', 'properties@jbangdev', 'java'); jbang.exec('hello.java', '"Java Script"') And in package.json: { "scripts": { "test": "node test.js" }, "devDependencies": { "@jbangdev/jbang": "^0.1.4" } } With a hello.java that contains: //DEPS com.github.lalyos:jfiglet:0.0.8 import com.github.lalyos.jfiglet.FigletFont; class hello { public static void main(String... args) throws Exception { System.out.println(FigletFont.convertOneLine( "Hello " + ((args.length>0)?args[0]:"jbang"))); ;; } } When you run this using npm run test it will automatically install JBang and Java if necessary and you get an output like: In this case JBang was already installed but it did go transparently and downloaded Java 18 as one of the scripts was called with --java 18. The opportunities are endless! jbang-npm was contributed by Peter Thomas and Pallafor Yodis from Karate hall of fame. Very much appreciated and looking forward to see what you make happen with Java Script in your JavaScript! Have fun! ### [JBang Rewind 2021!](/learn/jbang-rewind-2021/) We just closed on a very busy 2021 for JBang, and especially in the last few weeks, things progressed quite a bit. Therefore, I thought it would be nice to give a quick overview to recognize the progress and give you a chance to catch up! December 31st JDoodle updates their iOS and Android versions to include JBang support. You can now edit and run Java/JBang on the go. December 29th Guide to JBang posted on baeldung.com December 25th Free IntelliJ License giveaway for one recognized JBang contributor following the steps outlined here. It is a time of writing still open; thus, if you ever opened an issue, contributed an idea or code, or want to contribute, it is still possible to get it! December 22nd IntelliJ Plugin made available. Provides direct execution with JBang, content assist for //DEPS and other keywords, and more. December 1st JBang was the first entry of 2021 Java Advent Calendar. November 29th JDoodle added JBang to its list of supported languages. Making it possible to edit/write Java code using dependencies in a browser without login and additionally embed JBang examples in your blog/guides. November 24th Microsoft made a JBang catalog available providing easy launch of Minecraft and Playwright CLI. And in between, we had four releases of JBang in December with features and bug fixes - heading towards the mythical 1.0 release. The latest one is 0.86.0 which came on the second anniversary of JBang - or at least the first tweet about it. Here, one fix was how JBang deals with lib vs. libs that came from a conversation around using JBang in serverless lambda functions. Nándor Holozsnyák just made a blog showing his explorations into this. I was happy to see this as Nandor looked into this because he felt JBang enabled a simple scripting approach comparable to Python/Node - but for Java. That’s precisely what JBang is about - enabling easy use of Java - I’m expecting to see more enablement of using JBang for more than just scripts in 2022! Here is to make 2022 about bringing JBang to version 1.0 and making Java fun! ### [JEP 408: Simple Web Server with JBang](/learn/jep-408-simple-web-server-with-jbang/) In March JEP: 408: Simple Web Server was announced. One of JEP 408’s goals is to make an easy to use web server available with Java using java -m jdk.httpserver. It is a nice idea and I’m looking forward to see it happen. That said - you do not need to wait for Java 18+ to get this. You can use jbang httpd@jbangdev today with Java 1.8 and upwards. It is a script I wrote in a few hours after seeing yet another tweet asking for a simple to use webserver. Like JEP 408 it only uses classes available in the JDK, meaning zero additonal download and setup besides installing jbang. By default, httpd@jbangdev will serve the current directory on https://localhost:8000. Below is an example of using httpd@jbangdev to serve this website locally from its statically generated Jekyll _site folder on port 8080. $ jbang httpd@jbangdev -p 8080 -d _site [jbang] Building jar... Serving HTTP on 0.0.0.0 port 8080 (http://0.0.0.0:8080/) from /Users/max/code/jbangdev/jbang-catalog/_site ... 127.0.0.1 - - [04/Jun/2021:00:50:36 +0200] "GET /" 200 - 127.0.0.1 - - [04/Jun/2021:00:50:36 +0200] "GET /assets/css/main.css" 200 - 127.0.0.1 - - [04/Jun/2021:00:50:36 +0200] "GET /assets/js/main.min.js" 200 - 127.0.0.1 - - [04/Jun/2021:00:50:36 +0200] "GET /assets/images/logo.png" 200 - 127.0.0.1 - - [04/Jun/2021:00:50:36 +0200] "GET /assets/images/carbon-deps.png" 200 - 127.0.0.1 - - [04/Jun/2021:00:50:36 +0200] "GET /assets/images/carbon-install.png" 200 - 127.0.0.1 - - [04/Jun/2021:00:50:36 +0200] "GET /assets/images/carbon-java.png" 200 - 127.0.0.1 - - [04/Jun/2021:00:50:36 +0200] "GET /assets/images/feature-ide.png" 200 - 127.0.0.1 - - [04/Jun/2021:00:50:36 +0200] "GET /assets/images/feature-version-juggle.svg" 200 - 127.0.0.1 - - [04/Jun/2021:00:50:36 +0200] "GET /assets/images/feature-appstore.png" 200 - 127.0.0.1 - - [04/Jun/2021:00:50:36 +0200] "GET /assets/images/slider/bg-1.jpg" 200 - As with any other jbang capable application you can also choose to install it in your PATH using jbang app install http@jbangdev. Once you do that you can run it from anywhere using just httpd. In case you already have application called httpd you can use --name to give it a more unique name: $ jbang app install --name jhttpd httpd@jbangdev [jbang] Command installed: jhttpd $ jhttpd Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) from /Users/max/code/personal/jbangdev/jbang.dev ... Enjoy - and remember you can find httpd@jbangdev and other apps in JBang AppStore. ### [JBang Avatar](/learn/jbang-avatar/) It is getting very close to the first anniversary of first commit of JBang. To celebrate I looked around to see what we could do and was reminded about good old Gource. With Gource you can render the history of a git repository and get a nice animated video, including flying icons for each author. Trouble is that by default they are just faceless heads - lets get some faces to all those names. I found a perl script that took all committer emails and located their gravatar image; but it was Perl…​so lets make that java instead and use JBang. The full example is available here, below I’ll walk through the various interesting parts. If you want to just directly run it do this: jbang https://github.com/jbangdev/jbang/blob/HEAD/examples/grabavatars.java To begin with you just get started with jbang init -t cli grab-avatar. This will create a file named grabavatar.java with some pre-seeded code that does a Hello World! style app using picocli plus it will mark the file as executable. Setup dependencies and main class Then in top we are going to add a few dependencies: Dependencies The first line is the magic "shebang" line that bash and other shells will interpret but for java it is just a comment. This is what allows you to run the script directly using ./grab-avatar The dependencies used are as follows: Picocli for annotation based command line parsing and help generation. zt-exec is a nice library to execute processes in a nice fluent way. commons-coded just because default Java don’t have a one-liner for MD5 hash generation. No Operation slf4j to avoid the default noise slf4j generates. When you run ./grabavatars.java or jbang grabavatars.java these starting lines with //DEPS in them will be parsed and JBang fetches the dependencies to use during compilation and execution of your script. The next is the class declaration which is mainly just annotations to define the main picocli command. class declaration Iteration over execution Then the meat of the code is to run git log --pretty=format:%ae|%an" which will return a line for every commit with email and author, like: max@xam.dk|Max Rydahl Andersen To do this I use zt-exec which has this great "one-liner" to run the process and for every line output let me process it. processing command MD5 and Download And finally for every identified email and author - generates MD5 and download the url. processing command All together now Now to run this when I have it locally is just: ./grab-avatars in root of a git repository. But you can also run this directly from a url using: jbang https://github.com/jbangdev/jbang/blob/HEAD/examples/grab-avatars And then you can go full out and render with gource - notice the --user-image-dir .git/avatar: gource --title JBang --stop-at-end --user-image-dir .git/avatar --seconds-per-day 0.05 --auto-skip-seconds 1 -1280x720 --hide filenames -o - | ffmpeg -y -r 60 -f image2pipe -vcodec ppm -i - -vcodec libx264 -preset ultrafast -pix_fmt yuv420p -crf 1 -threads 0 -bf 0 gource.mp4 Then you get a video which with a little bit of magic of video editing that looks something like this: {#youtube "da0CuVbvWLg" /} And that is about it - showed how to get started with jbang to port an existing perl script to java using a few dependencies. See you next time! ### [Beyond Hello World!](/learn/beyond-hello-world/) A simple script you can run with jbang is "Hello World" using jshell (.jsh). System.out.println("Hello " + ((args.length>0)?args[0]:"jbang")); You can run the above by putting it in a file named: hello.jsh and run jbang hello.jsh. To expand on this we will make a Java file (.java) which uses an external dependency to make the text a bit more fancy. In this case we’ll use a library called jfiglet. We use //DEPS com.github.lalyos:jfiglet:0.0.8 to declare this script should run using the dependency with group id: com.github.lalyos, artifact id: jfiglet and with the version 0.0.8. These are called "artifacts coordinates" or "maven coordinates" as the system originates from the Maven build system; but is today used in almost all Java projects. You can use sites like https://mvnrepository.com to locate the coordinates of projects, and otherwise find them on the website or source repository of the project them self. Below is a repl.it instance preconfigured with jbang ready to run hello.java using the figlet dependency. Try and press the Run button to see how it works and then try edit the code to see how it pick up the changes between each run. ## Pages ### [tree/index.md](/tree/) Expecting a Tree ? Try j! https://jbang.dev/tree Hint 1 Hint 2 - Thanks to Milen Dyankov's for the idea :) ### [AppStore](/appstore/) JBang AppStore Below is an index of JBang aliases available on GitHub in JBang Catalogs. To use it, Download or Install JBang or run the following command from any bash compatible terminal: curl -Ls https://sh.jbang.dev | bash -s - app setup or in a Windows Powershell: iex "& { $(iwr https://ps.jbang.dev) } app setup" With JBang installed you can run any of the aliases from the JBang AppStore!, i.e. jbang jreleaser@jreleaser to run jreleaser. NOTE: Always verify a script code before executing it and know if your new catalog is missing it might just be because the index job have not run yet. Loading catalog… Failed to load the AppStore catalog. Please try refreshing the page. (function() { const ITEMS_PER_PAGE = 20; // groups per page let allGroups = []; // [{repo, icon, owner, name, stars, link, aliases: [...]}] let filteredGroups = []; let currentPage = 1; const searchInput = document.getElementById('appstore-search'); const countBadge = document.getElementById('appstore-count'); const loadingEl = document.getElementById('appstore-loading'); const errorEl = document.getElementById('appstore-error'); const contentEl = document.getElementById('appstore-content'); const groupsEl = document.getElementById('appstore-groups'); const paginationEl = document.getElementById('appstore-pagination').querySelector('menu'); // Read ?q= from URL const params = new URLSearchParams(window.location.search); if (params.get('q')) { searchInput.value = params.get('q'); } // Fetch data fetch('/assets/data/jbang-appstore.json') .then(r => { if (!r.ok) throw new Error(r.status); return r.json(); }) .then(data => { allGroups = (data.catalogs || []).map(function(cat) { return { repo: cat.owner + '/' + cat.name, icon: cat.icon, owner: cat.owner, name: cat.name, stars: cat.stars || 0, link: cat.link || ('https://github.com/' + cat.owner + '/' + cat.name), aliases: (cat.aliases || []).map(function(a) { return { command: a.c, description: a.d || null }; }) }; }); loadingEl.style.display = 'none'; contentEl.style.display = 'block'; applyFilter(); }) .catch(() => { loadingEl.style.display = 'none'; errorEl.style.display = 'block'; }); // Search searchInput.addEventListener('input', function() { currentPage = 1; applyFilter(); // Update URL without reload const url = new URL(window.location); if (this.value) url.searchParams.set('q', this.value); else url.searchParams.delete('q'); history.replaceState(null, '', url); }); function applyFilter() { const q = searchInput.value.toLowerCase().trim(); if (!q) { filteredGroups = allGroups; } else { filteredGroups = []; for (const g of allGroups) { const matchingAliases = g.aliases.filter(a => (a.command && a.command.toLowerCase().includes(q)) || (a.description && a.description.toLowerCase().includes(q)) || g.repo.toLowerCase().includes(q) ); if (matchingAliases.length > 0) { filteredGroups.push({...g, aliases: matchingAliases}); } } } const totalAliases = filteredGroups.reduce((sum, g) => sum + g.aliases.length, 0); countBadge.textContent = totalAliases + ' aliases in ' + filteredGroups.length + ' repos'; render(); } function render() { const totalPages = Math.max(1, Math.ceil(filteredGroups.length / ITEMS_PER_PAGE)); if (currentPage > totalPages) currentPage = totalPages; const start = (currentPage - 1) * ITEMS_PER_PAGE; const pageGroups = filteredGroups.slice(start, start + ITEMS_PER_PAGE); // Render groups groupsEl.innerHTML = pageGroups.map(g => ` ${esc(g.owner)}/${esc(g.name)} ${g.stars > 0 ? `★ ${g.stars.toLocaleString()}` : ''} ${g.aliases.map(a => ` ${esc(a.command)} ${a.description ? `${a.description} ` : ''} `).join('')} `).join(''); // Render pagination if (totalPages ← Prev `; const range = pageRange(currentPage, totalPages); for (const p of range) { if (p === '...') { html += `… `; } else { html += `${p} `; } } html += `Next → `; paginationEl.innerHTML = html; } } // Pagination clicks document.getElementById('appstore-pagination').addEventListener('click', function(e) { const btn = e.target.closest('[data-page]'); if (!btn || btn.disabled) return; currentPage = parseInt(btn.dataset.page); render(); groupsEl.scrollIntoView({behavior: 'smooth', block: 'start'}); }); // Copy clicks (delegated) — click button, code, or anywhere in the alias row groupsEl.addEventListener('click', function(e) { const cell = e.target.closest('.appstore-alias-cell'); if (!cell) return; // Don't copy when clicking links in descriptions if (e.target.closest('a')) return; const btn = cell.querySelector('.appstore-copy'); if (!btn) return; const cmd = btn.dataset.cmd; navigator.clipboard.writeText(cmd).then(() => { if (window.showCopyToast) window.showCopyToast('Copied: ' + cmd); }); }); function pageRange(current, total) { if (total i + 1); const pages = []; pages.push(1); if (current > 3) pages.push('...'); for (let i = Math.max(2, current - 1); i ### [contribute.md](/contribute/) Contribute to JBang JBang is opensource and developed on GitHub at {{ site.github }}. Feel free to join the contributors by opening issues, pull-requests or just drop by with a comment or idea. See Contributing guide for details on how to build and develop on JBang. ### [JBang Download Stats](/usage/downloads/) JBang Download Stats Live download statistics for jbangdev/jbang GitHub releases, fetched straight from the GitHub Releases API in your browser (inspired by grev). Two things are done differently here: version.txt is separated out — it is the tiny file JBang downloads on every update check, so its count dwarfs real installs and would otherwise skew the picture — and the x-axis follows real time (each release sits at its actual publish date), not evenly spaced release tags, so bursts and quiet periods show honestly. Loading release data from GitHub… – Releases – Latest release – version.txt checks update-check pings – Installer downloads .zip / .tar binaries Include pre-releases Logarithmic y-axis Dates on x-axis Refresh from GitHub Installer downloads per release (real installs) Total downloads each release's binary artifacts have accumulated to date, placed at the moment the release was published. Signatures (.asc), checksums and version.txt are excluded. version.txt update-checks per release (separated out) Downloads of the version.txt asset — overwhelmingly automated update checks, not installs. Shown on its own axis so it never distorts the installer chart above. Per-release detail Release Published Installers version.txt Checksums / sigs Total Note: GitHub reports each asset's cumulative download count to date, so a bar shows a release's lifetime downloads positioned at its publish date — not activity within that period. Data is cached in your browser for a few hours to stay within the GitHub API rate limit. .dlstats-root { /* dataviz reference palette (validated) */ --surface-1: #fcfcfb; --page-plane: #f9f9f7; --text-primary: #0b0b0b; --text-secondary: #52514e; --text-muted: #898781; --grid: #e1e0d9; --axis: #c3c2b7; --series-install: #2a78d6; /* blue */ --series-check: #eb6834; /* orange */ --border: rgba(11,11,11,0.10); max-width: 980px; margin: 0 auto; } @media (prefers-color-scheme: dark) { .dlstats-root { --surface-1: #1a1a19; --page-plane: #0d0d0d; --text-primary: #ffffff; --text-secondary: #c3c2b7; --text-muted: #898781; --grid: #2c2c2a; --axis: #383835; --series-install: #3987e5; --series-check: #d95926; --border: rgba(255,255,255,0.10); } } .dlstats-root p { color: var(--text-secondary); line-height: 1.55; } .dlstats-note { font-size: 0.9em; } .dlstats-status { padding: 24px; text-align: center; color: var(--text-secondary); } .dlstats-status.error { color: #d03b3b; } .dlstats-tiles { display: flex; gap: 30px; background-color: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); margin: 24px 0; flex-wrap: wrap; } @media (prefers-color-scheme: dark) { .dlstats-tiles { background-color: var(--surface-1); box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); } } .tile { text-align: center; flex: 1; min-width: 120px; padding: 10px 15px; } .tile + .tile { border-left: 1px solid #e0e0e0; } @media (prefers-color-scheme: dark) { .tile + .tile { border-left-color: #333; } } .tile-value { font-size: 2em; font-weight: bold; color: #4a90e2; line-height: 1.1; font-variant-numeric: tabular-nums; white-space: nowrap; } .tile-label { display: block; margin-top: 8px; font-size: 1em; color: #666; text-transform: uppercase; } .tile-sub { font-size: 0.8em; color: var(--text-muted); } .dlstats-controls { display: flex; flex-wrap: wrap; align-items: center; gap: 18px; margin: 8px 0 28px; font-size: 0.92em; color: var(--text-secondary); } .dlstats-controls label { cursor: pointer; user-select: none; } .dlstats-controls button { border: 1px solid var(--border); background: var(--surface-1); color: var(--text-primary); border-radius: 6px; padding: 6px 12px; cursor: pointer; } .dlstats-controls button:hover { background: var(--page-plane); } .dlstats-cache { color: var(--text-muted); font-size: 0.85em; } .dlstats-chart { margin: 32px 0; } .dlstats-chart h2 { margin-bottom: 4px; color: var(--text-primary); } .dlstats-chart h2 small { font-weight: 400; color: var(--text-muted); font-size: 0.6em; } .chart-caption { font-size: 0.88em; margin-top: 0; } .chart-holder { position: relative; height: 340px; } .table-scroll { overflow-x: auto; } #dlstats-table { width: 100%; border-collapse: collapse; font-size: 0.9em; } #dlstats-table th, #dlstats-table td { border-bottom: 1px solid var(--grid); padding: 7px 10px; text-align: left; color: var(--text-secondary); } #dlstats-table th { color: var(--text-primary); } #dlstats-table td.num, #dlstats-table th.num { text-align: right; font-variant-numeric: tabular-nums; } #dlstats-table tbody tr:hover { background: var(--page-plane); } (function () { "use strict"; var OWNER = "jbangdev", REPO = "jbang"; var CACHE_KEY = "jbang-dlstats:" + OWNER + "/" + REPO; var CACHE_TTL_MS = 6 * 60 * 60 * 1000; // 6 hours var css = getComputedStyle(document.querySelector(".dlstats-root")); var COLOR = { install: css.getPropertyValue("--series-install").trim() || "#2a78d6", check: css.getPropertyValue("--series-check").trim() || "#eb6834", grid: css.getPropertyValue("--grid").trim() || "#e1e0d9", axis: css.getPropertyValue("--axis").trim() || "#c3c2b7", text: css.getPropertyValue("--text-secondary").trim() || "#52514e", muted: css.getPropertyValue("--text-muted").trim() || "#898781" }; var statusEl = document.getElementById("dlstats-status"); var bodyEl = document.getElementById("dlstats-body"); var cacheEl = document.getElementById("dlstats-cache"); // --- asset classification ------------------------------------------------ function classify(name) { if (name === "version.txt") return "check"; if (/\.(asc|sha256|sig)$/i.test(name)) return "sig"; if (/^checksums/i.test(name)) return "sig"; if (/version\.txt\.(asc|sha256)$/i.test(name)) return "sig"; return "install"; } function summarize(release) { var installs = 0, checks = 0, sigs = 0; (release.assets || []).forEach(function (a) { var kind = classify(a.name); if (kind === "install") installs += a.download_count; else if (kind === "check") checks += a.download_count; else sigs += a.download_count; }); return { tag: release.tag_name, name: release.name || release.tag_name, prerelease: !!release.prerelease, published: release.published_at || release.created_at, url: release.html_url, installs: installs, checks: checks, sigs: sigs }; } // --- data fetching (paginated + cached) ---------------------------------- function readCache() { try { var raw = localStorage.getItem(CACHE_KEY); if (!raw) return null; var obj = JSON.parse(raw); if (!obj || !obj.at || !Array.isArray(obj.releases)) return null; return obj; } catch (e) { return null; } } function writeCache(releases) { try { localStorage.setItem(CACHE_KEY, JSON.stringify({ at: Date.now(), releases: releases })); } catch (e) { /* ignore quota */ } } function fetchAllReleases() { var out = []; function page(p) { var url = "https://api.github.com/repos/" + OWNER + "/" + REPO + "/releases?per_page=100&page=" + p; return fetch(url, { headers: { "Accept": "application/vnd.github+json" } }) .then(function (res) { if (res.status === 403 || res.status === 429) { var err = new Error("rate-limited"); err.rate = true; throw err; } if (!res.ok) throw new Error("GitHub API error " + res.status); return res.json(); }) .then(function (batch) { out = out.concat(batch.map(summarize)); if (batch.length === 100 && p ' + r.tag + '' + (r.prerelease ? ' (pre)' : '') + '' + '' + new Date(r.published).toLocaleDateString() + '' + '' + fmt(r.installs) + '' + '' + fmt(r.checks) + '' + '' + fmt(r.sigs) + '' + '' + fmt(total) + ''; tbody.appendChild(tr); }); } function renderAll() { var rows = activeReleases(); renderKPIs(rows); renderTable(rows); if (installChart) installChart.destroy(); if (checkChart) checkChart.destroy(); installChart = makeBarChart("chart-installs", rows, "installs", "Installer downloads", COLOR.install); checkChart = makeBarChart("chart-checks", rows, "checks", "version.txt checks", COLOR.check); } function showData(releases, cachedAt) { ALL = releases; statusEl.hidden = true; bodyEl.hidden = false; cacheEl.textContent = cachedAt ? ("cached " + ageText(cachedAt)) : ""; renderAll(); } function loadFromGitHub() { statusEl.hidden = false; statusEl.classList.remove("error"); statusEl.textContent = "Loading release data from GitHub…"; bodyEl.hidden = true; fetchAllReleases() .then(function (releases) { writeCache(releases); showData(releases, Date.now()); }) .catch(function (err) { var cached = readCache(); if (cached) { showData(cached.releases, cached.at); cacheEl.textContent = "GitHub unavailable – showing cached data (" + ageText(cached.at) + ")"; } else { statusEl.classList.add("error"); statusEl.textContent = err && err.rate ? "GitHub API rate limit reached (60 requests/hour for anonymous users). Please try again later." : "Could not load release data: " + (err ? err.message : "unknown error"); } }); } // wire controls document.getElementById("opt-prereleases").addEventListener("change", renderAll); document.getElementById("opt-log").addEventListener("change", renderAll); document.getElementById("opt-dates").addEventListener("change", renderAll); document.getElementById("opt-refresh").addEventListener("click", function () { try { localStorage.removeItem(CACHE_KEY); } catch (e) {} loadFromGitHub(); }); // boot: use fresh cache if available, else fetch var cached = readCache(); if (cached && (Date.now() - cached.at) ### [Download](/download/) Download JBang .tab-container { margin: 20px 0; } .tab-buttons { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 20px; justify-content: center; } .tab-button { padding: 10px 14px; background: #f5f5f5; border: none; border-radius: 5px; cursor: pointer; font-size: 0.85rem; display: flex; flex-direction: column; align-items: center; gap: 4px; min-width: 80px; text-align: center; } .tab-button i { font-size: 32px; margin-bottom: 4px; } .tab-button:hover { background: #e5e5e5; } .tab-button.active { background: #0066cc; color: white; } .tab-content { display: none; padding: 20px; border-radius: 5px; background: #f9f9f9; } .tab-content.active { display: block; } pre { background: #fff; padding: 15px; border-radius: 5px; } pre:hover { background: #f5f5f5; } Note: Java is not required to be installed - JBang will automatically download a Java version if no Java 8 or higher Java Development Kit (JDK) version is found. Windows Linux macOS Docker GitHub Actions Maven Gradle JavaScript Python curl/iex Powershell: iex "& { $(iwr https://ps.jbang.dev) } app setup" GitBash/cygwin/mingwin/WSL: curl -Ls https://sh.jbang.dev | bash -s - app setup Chocolatey choco install jbang Scoop scoop bucket add jbangdev https://github.com/jbangdev/scoop-bucket scoop install jbang mise mise use -g jbang gg.cmd .\gg.cmd jbang Manual Unzip the latest binary release, put the jbang-[version]/bin folder in to your $PATH and you are set. Zero install You can run download and run jbang directly using Powershell with: iex "& { $(iwr https://ps.jbang.dev) } [jbang arguments]" or if you have bash based shell/terminal: curl -Ls https://sh.jbang.dev | bash -s - [jbang arguments] curl curl -Ls https://sh.jbang.dev | bash -s - app setup SDKMAN! sdk install jbang asdf asdf plugin add jbang asdf install jbang latest mise mise use -g jbang gg.cmd ./gg.cmd jbang Fedora Copr For CentOS, Fedora, Mageia and OpenSuse: sudo dnf copr enable @jbangdev/jbang sudo dnf install jbang Manual Unzip the latest binary release, put the jbang-[version]/bin folder in to your $PATH and you are set. Zero install curl -Ls https://sh.jbang.dev | bash -s - [jbang arguments] curl curl -Ls https://sh.jbang.dev | bash -s - app setup SDKMAN! sdk install jbang Homebrew brew install jbangdev/tap/jbang asdf asdf plugin add jbang asdf install jbang latest mise mise use -g jbang gg.cmd ./gg.cmd jbang Manual Unzip the latest binary release, put the jbang-[version]/bin folder in to your $PATH and you are set. Zero install curl -Ls https://sh.jbang.dev | bash -s - [jbang arguments] docker.io docker run -v `pwd`:/ws --workdir=/ws jbangdev/jbang-action helloworld.java Quay.io docker run -v `pwd`:/ws --workdir=/ws quay.io/jbangdev/jbang-action helloworld.java setup-jbang action: - name: Setup JBang uses: jbangdev/setup-jbang@main - name: properties run: | jbang properties@jbangdev Install and see details at Github Action Marketplace Plugin to run jbang as part of a Apache Maven build available from jbang-maven-plugin <plugin> <groupId>dev.jbang</groupId> <artifactId>jbang-maven-plugin</artifactId> <version>0.0.7</version> <executions> <execution> <id>run</id> <phase>process-resources</phase> <goals> <goal>run</goal> </goals> <configuration> <script>hello.java</script> </configuration> </execution> </executions> </plugin> Plugin to run jbang as part of a Gradle build available from jbang-gradle-plugin plugins { id 'dev.jbang' version '0.2.0' } npx npx @jbangdev/jbang app setup npm // package.json: { "scripts": { "test": "node test.js" }, "devDependencies": { "@jbangdev/jbang": "^0.1.4" } } // test.js: #! /usr/bin/env node const jbang = require('@jbangdev/jbang'); jbang.exec('properties@jbangdev'); pipx pipx jbang app setup uvx uvx jbang app setup pip/jupiter notebook !pip install jbang import jbang jbang.exec('properties@jbangdev') document.addEventListener('DOMContentLoaded', function() { // OS detection function detectOS() { const platform = navigator.platform.toLowerCase(); const userAgent = navigator.userAgent.toLowerCase(); // More robust OS detection if (userAgent.includes('windows')) return 'windows'; if (userAgent.includes('mac os x')) return 'mac'; if (userAgent.includes('linux')) return 'linux'; // Fallback to platform checking if (platform.includes('win')) return 'windows'; if (platform.includes('mac')) return 'mac'; if (platform.includes('linux')) return 'linux'; return 'windows'; // default fallback } // Tab switching functionality function switchTab(tabId) { // Hide all tabs document.querySelectorAll('.tab-content').forEach(tab => { tab.classList.remove('active'); }); document.querySelectorAll('.tab-button').forEach(button => { button.classList.remove('active'); }); // Show selected tab const selectedTab = document.getElementById(tabId); const selectedButton = document.querySelector(`[data-tab="${tabId}"]`); if (selectedTab) selectedTab.classList.add('active'); if (selectedButton) selectedButton.classList.add('active'); } // Add click handlers to all tab buttons document.querySelectorAll('.tab-button').forEach(button => { button.addEventListener('click', () => { switchTab(button.dataset.tab); }); }); // Show the OS-specific tab on load try { const detectedOS = detectOS(); console.log('Detected OS:', detectedOS); switchTab(detectedOS); } catch (error) { console.error('Error during OS detection:', error); // Fallback to windows tab if there's an error switchTab('windows'); } }); ### [JBang Runs Everywhere](/everywhere/) jbang curl -Ls https://sh.jbang.dev | bash -s - podman run -it jbangdev/jbang docker run -it jbangdev/jbang npx @jbangdev/jbang uvx jbang pipx jbang gavsearch@jbangdev pkl@apple jdbc@quarkiverse/quarkus-mcp-servers playwright@microsoft arthas@alibaba mcp-proxy@quarkus.ai container@quarkiverse/quarkus-mcp-servers camel@apache/camel com.h2database:h2:2.3.232 Spin! Copy Copied! .slot-machine-container { max-width: 1000px; margin: 2rem auto; text-align: center; padding: 0 1rem; } .slot-machine { display: flex; justify-content: center; gap: 2rem; background: #2a2a2a; padding: 2rem; border-radius: 1rem; box-shadow: 0 0 20px rgba(0,0,0,0.3); flex-wrap: nowrap; } .slot-viewport { background: #fff; padding: 0.75rem; border-radius: 0.5rem; height: 70px; position: relative; overflow: hidden; cursor: grab; touch-action: pan-y; margin: 0.5rem 0; } .slot-viewport.dragging { cursor: grabbing; } .slot-wheel { position: absolute; width: 100%; left: 0; top: 0; transform: translateY(0); transition: transform 2.5s cubic-bezier(0.21, 0.53, 0.29, 0.99); user-select: none; } .slot-wheel.no-transition { transition: none; } .launcher { width: 500px; flex: 0 1 500px; min-width: 0; } .target { width: 450px; flex: 0 1 450px; min-width: 0; } .slot-item { height: 70px; display: flex; align-items: center; justify-content: center; font-family: monospace; font-size: 0.95rem; color: #333; padding: 0 0.5rem; white-space: nowrap; text-align: center; } .launcher .slot-item { justify-content: flex-end; text-align: right; } .target .slot-item { justify-content: flex-start; text-align: left; } .slot-viewport::before, .slot-viewport::after { content: ''; position: absolute; left: 0; right: 0; height: 25px; z-index: 1; pointer-events: none; } .slot-viewport::before { top: 0; background: linear-gradient(to bottom, rgba(255,255,255,1) 0%, rgba(255,255,255,0) 100%); } .slot-viewport::after { bottom: 0; background: linear-gradient(to top, rgba(255,255,255,1) 0%, rgba(255,255,255,0) 100%); } .slot-machine-controls { margin-top: 2rem; display: flex; justify-content: center; align-items: center; gap: 1rem; flex-wrap: wrap; } #spin-button, #copy-button, .slot-machine-controls .btn { font-size: 1.2rem; padding: 0.8rem 2rem; } .copy-feedback { position: absolute; background: #4BB543; color: white; padding: 0.5rem 1rem; border-radius: 4px; opacity: 0; transition: opacity 0.3s ease-in-out; pointer-events: none; } .copy-feedback.visible { opacity: 1; } @media (max-width: 1050px) { .slot-machine { flex-direction: column; align-items: center; width: 90%; margin: 0 auto; } .launcher, .target { width: 100%; max-width: 550px; flex: none; } } @media (max-width: 600px) { .slot-item { font-size: 0.85rem; } .slot-machine { padding: 1.5rem 1rem; width: 95%; } .slot-viewport { height: 60px; } .slot-item { height: 60px; } } document.addEventListener('DOMContentLoaded', function() { const wheels = document.querySelectorAll('.slot-wheel'); const viewports = document.querySelectorAll('.slot-viewport'); const spinButton = document.getElementById('spin-button'); const copyButton = document.getElementById('copy-button'); const copyFeedback = document.getElementById('copy-feedback'); let isSpinning = false; // Store original items for each wheel const originalItems = []; // Set up each wheel with cloned items wheels.forEach(wheel => { // Store the original items to use as a reference const initialItems = Array.from(wheel.querySelectorAll('.slot-item')); originalItems.push(initialItems); // Clear the wheel wheel.innerHTML = ''; // Now add many sets of the original items // First add an invisible padding item const paddingTop = document.createElement('div'); paddingTop.className = 'slot-item'; paddingTop.style.visibility = 'hidden'; wheel.appendChild(paddingTop); // Add 5 sets of the original items for (let i = 0; i { wheel.appendChild(item.cloneNode(true)); }); } // Add another invisible padding item at the end const paddingBottom = document.createElement('div'); paddingBottom.className = 'slot-item'; paddingBottom.style.visibility = 'hidden'; wheel.appendChild(paddingBottom); }); // Place each wheel at a random starting position function resetWheels() { wheels.forEach((wheel, index) => { const items = originalItems[index]; const itemHeight = wheel.querySelector('.slot-item').offsetHeight; const viewportHeight = wheel.closest('.slot-viewport').offsetHeight; const offset = (viewportHeight - itemHeight) / 2; // Pick a random item to show (from 0 to items.length - 1) const randomIndex = Math.floor(Math.random() * items.length); // Set the wheel position to show this item (add 1 for the padding item) const position = -(randomIndex + 1) * itemHeight + offset; wheel.style.transition = 'none'; wheel.style.transform = `translateY(${position}px)`; }); } // Initialize wheels with a small delay to ensure styles are applied setTimeout(resetWheels, 100); // Helper function to get translation Y value function getTranslateY(element) { try { const transform = window.getComputedStyle(element).transform; if (transform === 'none') return 0; const matrix = new WebKitCSSMatrix(transform); return matrix.m42; } catch (e) { console.error('Error getting transform:', e); return 0; } } // Helper to safely set transform function setTransformY(element, y) { element.style.transform = `translateY(${y}px)`; } // Setup dragging viewports.forEach((viewport, index) => { const wheel = wheels[index]; const items = originalItems[index]; let isDragging = false; let startY = 0; let startWheelY = 0; function onDragStart(e) { if (isSpinning) return; isDragging = true; viewport.classList.add('dragging'); wheel.classList.add('no-transition'); startY = e.type === 'mousedown' ? e.clientY : e.touches[0].clientY; startWheelY = getTranslateY(wheel); } function onDragMove(e) { if (!isDragging) return; e.preventDefault(); const currentY = e.type === 'mousemove' ? e.clientY : e.touches[0].clientY; const deltaY = currentY - startY; // Move the wheel setTransformY(wheel, startWheelY + deltaY); } function onDragEnd() { if (!isDragging) return; isDragging = false; viewport.classList.remove('dragging'); // Snap to nearest item const itemHeight = wheel.querySelector('.slot-item').offsetHeight; const viewportHeight = viewport.offsetHeight; const offset = (viewportHeight - itemHeight) / 2; const currentY = getTranslateY(wheel); // Calculate the item index (compensate for offset) const indexFromTop = Math.round((currentY - offset) / -itemHeight); // Ensure we stay within valid range (1 to items.length) const validIndex = Math.max(1, Math.min(items.length, indexFromTop)); // Calculate the final position const finalY = -(validIndex * itemHeight) + offset; // Animate to final position wheel.classList.remove('no-transition'); setTransformY(wheel, finalY); } // Mouse events viewport.addEventListener('mousedown', onDragStart); window.addEventListener('mousemove', onDragMove); window.addEventListener('mouseup', onDragEnd); // Touch events viewport.addEventListener('touchstart', onDragStart); window.addEventListener('touchmove', onDragMove, { passive: false }); window.addEventListener('touchend', onDragEnd); }); // Spin function function spin() { if (isSpinning) return; isSpinning = true; wheels.forEach((wheel, wheelIndex) => { const items = originalItems[wheelIndex]; const itemHeight = wheel.querySelector('.slot-item').offsetHeight; const viewportHeight = wheel.closest('.slot-viewport').offsetHeight; const offset = (viewportHeight - itemHeight) / 2; // Choose a random final item const finalItemIndex = Math.floor(Math.random() * items.length); // Calculate the final position (add 1 for padding) const finalPosition = -((finalItemIndex + 1) * itemHeight) + offset; // Get current position const currentPosition = getTranslateY(wheel); // Calculate the exact target position to land directly on the final position // This ensures there's no need to reset at the end // We need to determine how many items to spin through to land at finalPosition let itemsToFinalPosition = finalItemIndex + 1; // +1 for padding // Determine current item index (adjusted for offset) const currentIndex = Math.round((currentPosition - offset) / -itemHeight); // Calculate items to spin through to reach final position let itemsToSpin = itemsToFinalPosition - currentIndex; // If we need to go backwards, add full rotation instead if (itemsToSpin { wheel.style.transition = 'transform 2.5s cubic-bezier(0.21, 0.53, 0.29, 0.99)'; setTransformY(wheel, targetPosition); wheel.addEventListener('transitionend', function onEnd() { // Directly ensure we're at the final position if (getTranslateY(wheel) !== finalPosition) { wheel.style.transition = 'none'; setTransformY(wheel, finalPosition); wheel.offsetHeight; // Force reflow wheel.style.transition = 'transform 2.5s cubic-bezier(0.21, 0.53, 0.29, 0.99)'; } // Clean up wheel.removeEventListener('transitionend', onEnd); // Allow spins again when all wheels are done if (wheelIndex === wheels.length - 1) { isSpinning = false; } }, { once: true }); }, 50); }); } // Function to get the current visible items in both slots function getCurrentCombination() { const result = []; wheels.forEach((wheel, wheelIndex) => { const items = originalItems[wheelIndex]; const itemHeight = wheel.querySelector('.slot-item').offsetHeight; const viewportHeight = wheel.closest('.slot-viewport').offsetHeight; const offset = (viewportHeight - itemHeight) / 2; // Get current position const currentPosition = getTranslateY(wheel); // Calculate current visible item index const currentIndex = Math.round((currentPosition - offset) / -itemHeight) - 1; // Get the text of the visible item (accounting for index boundaries) let safeIndex = currentIndex; if (safeIndex = items.length) safeIndex = items.length - 1; result.push(items[safeIndex].textContent); }); return result.join(' '); } // Add click handler to spin button spinButton.addEventListener('click', function() { if (!isSpinning) { spin(); } }); // Copy button click handler copyButton.addEventListener('click', function() { const combination = getCurrentCombination(); // Copy to clipboard navigator.clipboard.writeText(combination).then(() => { // Show feedback copyFeedback.classList.add('visible'); // Position the feedback near the button const buttonRect = copyButton.getBoundingClientRect(); copyFeedback.style.top = `${buttonRect.top - 40}px`; copyFeedback.style.left = `${buttonRect.left + buttonRect.width/2 - 40}px`; // Hide feedback after a delay setTimeout(() => { copyFeedback.classList.remove('visible'); }, 2000); }).catch(err => { console.error('Could not copy text: ', err); }); }); // Also add copy function to transitionend events for both wheels wheels.forEach((wheel, wheelIndex) => { wheel.addEventListener('transitionend', function() { // Update copy button text to show current combination copyButton.setAttribute('aria-label', `Copy: ${getCurrentCombination()}`); }); }); // If things get stuck, reset on window resize window.addEventListener('resize', function() { isSpinning = false; setTimeout(resetWheels, 100); }); // Auto-spin when page loads setTimeout(function() { spin(); }, 800); }); Take JBang for a spin, press copy and paste that command in your terminal. Anything JBang is not just for scripts. It is a tool that allows you to run any Java application you want from the command line. There is already a growing ecosystem of tools that use JBang, try some above or see AppStore for more! Everywhere JBang allows you and your users to run Java applications from the command line using the tools they already know and love. JBang is available on all major platforms, including Linux, macOS, and Windows. Anywhere With JBang, running Java has never been easier or more versatile! Whether you're a seasoned developer or just starting out, JBang offers a multitude of ways to get your Java applications up and running in no time. Directly from the Command Line: Simply use jbang yourfile.java to run your Java files instantly. Using Scripts: Embed dependencies directly in your scripts with //DEPS and run them effortlessly. Docker and Podman: Run your Java apps in isolated containers using docker run -it jbangdev/jbang or podman run -it jbangdev/jbang. Node Package Manager (NPM): Integrate with Node.js using npx @jbangdev/jbang for seamless Java execution. UV Package Manager )UVX): Use uvx jbang to run Java apps with super-fast packager for Python. Python Package Manager (PIPX): Execute Java applications with pipx run jbang for a classic Pythonic touch. No matter where you are or what platform you're on, JBang ensures that Java is always at your fingertips. Give it a spin and experience the power of Java, unleashed! ### [Getting Started with JBang](/getting-started/) Get Started with JBang in 5 Minutes JBang makes Java development effortless. Whether you're a seasoned developer or just starting with Java, JBang eliminates the complexity of traditional Java development. 🚀 Quick Installation Choose your platform and install JBang in seconds: macOS & Linux: curl -Ls https://sh.jbang.dev | bash -s - app setup Windows (PowerShell): iex "& { $(iwr https://ps.jbang.dev) } app setup" Docker: docker run -it jbangdev/jbang 📝 Your First JBang Script Create a file called hello.java: public class hello { public static void main(String[] args) { System.out.println("Hello, JBang!"); System.out.println("Java version: " + System.getProperty("java.version")); } } Run it instantly: jbang hello.java That's it! No compilation, no classpath setup, no build files. 🔧 Adding Dependencies JBang automatically downloads and manages dependencies. Add them directly in your source code: //DEPS org.springframework.boot:spring-boot-starter-web:3.2.0 //DEPS org.springframework.boot:spring-boot-starter-data-jpa:3.2.0 import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @SpringBootApplication @RestController public class app { @GetMapping("/") public String hello() { return "Hello from Spring Boot + JBang!"; } public static void main(String[] args) { SpringApplication.run(app.class, args); } } Run with: jbang app.java 🎯 Common Use Cases For Junior Developers Learning Java Fundamentals: //DEPS org.junit.jupiter:junit-jupiter:5.9.2 import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; public class CalculatorTest { @Test public void testAddition() { assertEquals(4, 2 + 2); } public static void main(String[] args) { // Run tests org.junit.platform.launcher.LauncherFactory .create() .execute(org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder .request() .selectors(org.junit.platform.engine.discovery.DiscoverySelectors .selectClass(CalculatorTest.class)) .build()); } } For Senior Developers Quick Prototyping: //DEPS io.quarkus:quarkus-resteasy-reactive:3.2.0.Final //DEPS io.quarkus:quarkus-hibernate-orm-panache:3.2.0.Final //DEPS io.quarkus:quarkus-jdbc-postgresql:3.2.0.Final import io.quarkus.runtime.Quarkus; import io.quarkus.runtime.annotations.QuarkusMain; @QuarkusMain public class QuickAPI { public static void main(String[] args) { Quarkus.run(args); } } DevOps Scripts: //DEPS com.fasterxml.jackson.core:jackson-databind:2.15.2 //DEPS org.apache.httpcomponents.client5:httpclient5:5.2.1 import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.hc.client5.http.classic.methods.HttpGet; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.HttpClients; public class HealthCheck { public static void main(String[] args) throws Exception { try (CloseableHttpClient client = HttpClients.createDefault()) { HttpGet request = new HttpGet("https://api.github.com/status"); String response = client.execute(request, response1 -> new String(response1.getEntity().getContent().readAllBytes())); ObjectMapper mapper = new ObjectMapper(); var status = mapper.readTree(response); System.out.println("GitHub Status: " + status.get("status").get("description")); } } } 🌟 Advanced Features Run from URLs jbang https://github.com/jbangdev/jbang-catalog/blob/main/jbang/Hello.java Use Different Java Versions //JAVA 17 //DEPS org.springframework.boot:spring-boot-starter-web:3.2.0 public class app { public static void main(String[] args) { System.out.println("Running on Java " + System.getProperty("java.version")); } } IDE Integration JBang works seamlessly with: IntelliJ IDEA: Full support with syntax highlighting and debugging VS Code: Java extension pack works out of the box Eclipse: Java development tools recognize JBang files Vim/Emacs: Syntax highlighting and code completion 📚 Next Steps Explore the AppStore: Visit jbang.dev/appstore to discover applications Join the Community: Follow us on GitHub and Twitter Read the Documentation: Check out our comprehensive docs Share Your Code: Create catalogs and share your JBang applications 🆘 Need Help? Documentation: jbang.dev/documentation GitHub Issues: github.com/jbangdev/jbang/issues Community: GitHub Discussions Ready to unleash the power of Java? Start building with JBang today! ### [IDE](/ide/) /* Force light mode for Oat CSS */ :root { color-scheme: light; } JBang IDE Support VS Code / VSCodium IntelliJ IDEA Eclipse Zed Vim/neovim Emacs repl.it GitPod JDoodle Jupyter VS Code / VSCodium The JBang extension works in both Visual Studio Code and VSCodium (a free, open-source VS Code distribution). Running jbang edit installs VSCodium by default with Java and JBang extensions pre-configured — ready to code immediately. Install Quickest: just run jbang edit hello.java — JBang installs VSCodium and the required extensions automatically. Manual install in VS Code: install JBang from the VS Marketplace. Manual install in VSCodium: install JBang from Open VSX. Features Automatic classpath synchronization from JBang directives Completion for //DEPS, //SOURCES, //FILES, and options Run and Debug via CodeLens Create scripts from templates, export native binaries, install as apps Per-script projects with java.import.jbang.projectPerScript Automatic annotation processor configuration build.jbang and jbang-catalog.json support Documentation Full documentation · Source on GitHub IntelliJ IDEA The JBang plugin makes Java, Kotlin, Groovy, JShell, and .jbang scripts first-class citizens in IntelliJ IDEA. It uses jbang info tools as the single source of truth — no module mutation, no Gradle/Maven modification. Install From Marketplace: Settings → Plugins → Marketplace → search "JBang" → Install Restart IntelliJ IDEA Open a folder with JBang scripts — the plugin auto-detects and syncs Early Access (EAP channel): Settings → Plugins → ⚙ → Manage Plugin Repositories… Add: https://plugins.jetbrains.com/plugins/eap/18257 Search "JBang" in Marketplace and install The EAP channel receives every release, giving you the latest features before they hit stable. Requirements IntelliJ IDEA 2026.2 or newer JBang CLI installed — or let the plugin install it from Settings → Tools → JBang Features Multiple JBang roots with isolated classpaths and JDKs Completion for directives, Maven coordinates, //SOURCES, //FILES, and catalog refs Run and Debug with gutter icons and run configurations Per-root //JAVA JDK registration Diagnostics for invalid directives, unresolved dependencies, and missing resources Create scripts from templates via New → JBang Script Documentation Full documentation · JetBrains Marketplace · Source on GitHub Eclipse The JBang plugin for Eclipse lets you import and synchronize JBang scripts as projects, with automatic dependency resolution and JDK configuration. Install From Eclipse Marketplace: Drag the Install button to your running Eclipse, or: Help → Eclipse Marketplace… → search "JBang" → Install Features Import JBang scripts as projects (Import Project… → JBang → JBang script) Synchronize JBang dependencies to existing projects via right-click Automatic JDK configuration from //JAVA Annotation processor detection Support for //DEPS, //SOURCES, and @Grab Documentation Eclipse Marketplace · Source on GitHub Zed Zed is a high-performance editor with Java support via Eclipse JDT Language Server. No native JBang plugin yet — use sandbox mode which sets up a temporary project with full classpath: jbang edit --sandbox --open=zed yourapp.java Vim/neovim Vim and neovim support Java via Eclipse JDT Language Server. No native JBang plugin yet — use sandbox mode: jbang edit --sandbox --open=nvim yourapp.java Emacs Emacs supports Java via Eclipse JDT Language Server. No native JBang plugin yet — use sandbox mode: jbang edit --sandbox --open=emacs yourapp.java repl.it repl.it is a free, online code editor that makes it easy to code, compile, run, and share in 50+ programming languages. JBang template for Repl.it GitPod GitPod is a free online IDE that allows you to create a dev environment for any GitHub repository. JBang container template for GitPod JDoodle JDoodle is a free online compiler, IDE and execution environment for Java, C, C++, PHP, Perl, Python, Ruby and many more. Try JBang on JDoodle Jupyter Notebook Jupyter Notebook is an open-source web application that allows you to create and share documents containing live code, equations, visualizations, and narrative text. With the JBang kernel, you can run Java code with JBang directly in Jupyter notebooks. Installation Install JupyterLab and the JBang kernel: # Install JupyterLab (requires Python) pip install jupyterlab # Install JBang kernel jbang install-kernel@jupyter-java --java 25 --enable-preview jbang Usage Launch JupyterLab and create a new notebook with the JBang kernel: jupyter lab In JupyterLab, create a new notebook and select "JBang" as the kernel. You can now write and execute Java code with JBang directives like //DEPS directly in your notebooks! Try Online Want to try it without installing? Use our online Jupyter environment powered by MyBinder. Learn More Visit the JBang Jupyter Kernel repository for more details and examples. ### [Launch JBang](/launch/) 🚀 Launching JBang JBang Launch is an experimental feature! Trying to open your application via JBang: jbang://... Click the button below to launch JBang: 🚀 Launch JBang Now If nothing happens, JBang might not be installed. JBang lets you run Java scripts with zero setup. It's fast. It's flexible. It's Java, reimagined. Why wait? Give it a spin. 🛠 You can still try it right now Install JBang Launch Or run the command manually in your terminal: 📋 Copy Command function getJBangURI() { if(window.location.search.startsWith("?jbang://")) { return window.location.search.substring(1); } const params = new URLSearchParams(window.location.search); if (params.has("uri")) { return decodeURIComponent(params.get("uri")); } // Check if any query parameter starts with jbang:// for (const [key, value] of params.entries()) { if (value.startsWith("jbang://")) { return decodeURIComponent(value); } } if (window.location.hash.startsWith("#jbang://")) { return decodeURIComponent(window.location.hash.slice(1)); } const path = window.location.pathname; if (path.startsWith("/launch/")) { return "jbang://" + decodeURIComponent(path.replace("/launch/", "")); } return null; } function parseToCLI(jbangUri) { //TODO: convert uri to cli command try { const uri = new URL(jbangUri); const segments = uri.pathname.split("/").filter(Boolean); const command = segments[0] || "run"; const target = segments[1] || ""; const args = uri.searchParams.get("args")?.split(",").join(" ") || ""; return `jbang ${command} ${target} ${args}`.trim(); } catch { return jbangUri; } } const jbangUri = getJBangURI(); document.getElementById("uri-display").textContent = jbangUri || "unknown"; if (jbangUri && jbangUri.startsWith("jbang://")) { const cliCmd = parseToCLI(jbangUri); document.getElementById("cli-command").textContent = cliCmd; // Add event listener for copy button /*document.getElementById("copy-btn").addEventListener("click", (e) => { e.preventDefault(); navigator.clipboard.writeText(cliCmd); });*/ // Add event listener for manual launch button document.getElementById("launch-btn").addEventListener("click", (e) => { e.preventDefault(); try { window.location.href = jbangUri; } catch (e) { console.log("Could not redirect to jbang:// URI"); } }); // Try to launch JBang immediately (like the original) try { window.location.href = jbangUri; } catch (e) { console.log("Could not redirect to jbang:// URI"); } // Show fallback after 3 seconds setTimeout(() => { document.getElementById("fallback").classList.remove("hidden"); }, 3000); } ### [JBang Leaderboard](/usage/leaderboard/) table { width: 100%; border-collapse: collapse; margin-bottom: 20px; } th, td { border: 1px solid #ddd; padding: 8px; text-align: left; } th { background-color: #f2f2f2; } .non-rated { margin-top: 20px; } .counters { display: flex; gap: 30px; background-color: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); } .counter { text-align: center; } .number { font-size: 2.5em; font-weight: bold; color: #4a90e2; } .label { display: block; margin-top: 8px; font-size: 1em; color: #666; text-transform: uppercase; } document.addEventListener("DOMContentLoaded", function () { const counters = document.querySelectorAll(".number"); counters.forEach(counter => { counter.innerText = '0'; const updateCounter = () => { const target = +counter.getAttribute('data-target'); const count = +counter.innerText; const increment = target / 200; if (count JBang Usage Leaderboard This is a work in progress. The data is collected from the JBang version checking here. See also the GitHub release download stats (installers vs. version.txt update-checks, over time). The data is updated once a day. It will show data for maximum last 3 months, starting from 2024-10-19. % is calculated as unique per approximate location info used for the http request. Meaning if the same approximate area makes multiple requests using the same version/country it is counted only once. NOTE: The data is an approximation. This is not seeing all invocations, just those that reach the version check endpoint. Meaning nothing behind proxies, nothing that runs jbang very fast (as in no time to do an update check) or just don't do an update check. The total count is unknowingly higher - thus just use this as a interesting data point - mainly for fun! 1244092 Checks 140 Countries 41 Java Vendors Top 10 Countries Position Country % of Total 1 United States 16.1% 2 Germany 9.5% 3 France 7.8% 4 Brazil 4.3% 5 Italy 4.2% 6 India 4.2% 7 Spain 4.0% 8 United Kingdom 3.4% 9 Netherlands 2.8% 10 Switzerland 2.2% Other Countries (in no particular order) Poland, Austria, Canada, Czechia, Belgium, Tunisia, Sweden, Russia, Portugal, Japan, Mexico, Argentina, China, Slovakia, Norway, Ukraine, Denmark, Finland, Vietnam, Indonesia, Ireland, Greece, Colombia, Israel, Hungary, Romania, South Korea, Malaysia, Serbia, Singapore, Thailand, Philippines, Türkiye, Egypt, Australia, Croatia, Chile, Bulgaria, Algeria, Costa Rica, Ecuador, Peru, Hong Kong SAR China, Bangladesh, Morocco, Saudi Arabia, New Zealand, Pakistan, Luxembourg, Slovenia, Taiwan, Panama, Kuwait, Dominican Republic, Kazakhstan, Bosnia & Herzegovina, Guatemala, Belarus, El Salvador, South Africa, Uruguay, Georgia, Montenegro, Paraguay, Nigeria, Iran, Cyprus, Kenya, United Arab Emirates, Venezuela, Lithuania, Jamaica, Estonia, North Macedonia, Albania, Angola, Malta, Sri Lanka, Lebanon, Palestinian Territories, Latvia, Oman, Qatar, Malawi, Trinidad & Tobago, Bolivia, Armenia, Uganda, Equatorial Guinea, Laos, Cape Verde, Cameroon, Mongolia, Puerto Rico, Liechtenstein, Cayman Islands, Ghana, Papua New Guinea, Yemen, Jordan, Zimbabwe, Ethiopia, Honduras, Nicaragua, Senegal, St. Martin, Mali, Rwanda, Madagascar, Barbados, Guinea, Zambia, Mozambique, Côte d’Ivoire, Martinique, Bahrain, Iceland, Libya, Moldova, Cambodia, Iraq, Nepal, Curaçao, Åland Islands, St. Barthélemy, St. Vincent & Grenadines, Bahamas, Mauritius, Azerbaijan Top 10 JBang Versions Position Version % of Total 1 0.141.0 19.1% 2 0.138.0 11.9% 3 0.137.0 8.2% 4 0.142.0 7.6% 5 0.139.3 6.5% 6 0.135.1 4.7% 7 0.132.1 4.7% 8 0.125.1 2.5% 9 0.129.0 2.2% 10 0.136.0 2.0% Other Versions (in no particular order) , 0.135.0, 0.123.0, 0.116.0, 0.126.1, 0.126.3, 0.117.1, 0.119.0, 0.127.18, 0.122.0, 0.124.0, 0.131.0, 0.121.0, 0.140.1, 0.114.0, 0.130.0, 0.126.2, 0.126.0, 0.132.0, 0.128.5, 0.111.0, 0.128.7, 0.115.0, 0.110.1, 0.118.0, 0.134.3, 0.97.0, 0.134.2, 0.106.3, 0.113.0, 0.108.0, 0.107.0, 0.127.15, 0.101.0, 0.104.0, 0.106.1, 0.110.0, 0.139.2, 0.109.0, 0.125.0, 0.70.0, 0.105.2, 0.112.4, 0.94.0, 0.102.0, 0.95.0, 0.92.2, 0.128.0, 0.103.1, 0.78.0, 0.96.0, 0.98.0, 0.103.2, 0.86.0, 0.91.0, 0.100.0, 0.93.0, 0.69.1, 0.85.1, 0.71.1, 0.72.0, 2.33.0, 0.88.0, 0.80.0, 0.87.0, 0.105.1, 0.103.0, 0.92.1, 0.99.1, 0.66.1, 0.96.4 Top 10 Java Versions Position Java Version % of Total 1 21.0 33.4% 2 25.0 24.3% 3 17.0 21.7% 4 26.0 5.4% 5 1.8 4.0% 6 11.0 3.3% 7 24.0 1.5% 8 Unknown 1.5% 9 23.0 1.5% 10 27.0 1.2% Other Java Versions (in no particular order) 22.0, 19.0, 20.0, 18.0, 27-ea.0, 28-internal.0, 25-loom.0, 28-ea.0, 9.0, 25-beta.0, 27-beta.0, 14.0, 28-beta.0, 27-internal.0, go.0, 16.0, 15.0, 12.0 Top 10 Java Vendors Position Vendor % of Total 1 Eclipse Adoptium 27.9% 2 Oracle Corporation 19.9% 3 Homebrew 7.9% 4 Amazon.com Inc. 7.7% 5 Ubuntu 6.7% 6 Azul Systems, Inc. 6.5% 7 Microsoft 3.9% 8 Red Hat, Inc. 3.7% 9 GraalVM Community 3.4% 10 2.4% Other Vendors (in no particular order) Arch Linux, BellSoft, JetBrains s.r.o., Debian, Eclipse OpenJ9, Temurin, OpenLogic, AdoptOpenJDK, SAP SE, Private Build, wolfi, Alpine, OpenLogic-OpenJDK, Apple Inc., Termux, Alibaba, ojdkbuild, MacPorts, Eclipse Foundation, tea.xyz, Artix Linux, Axiom JSC, Gentoo, OpenJDK BSD Porting Team, Harmonybrew, go1.22.5, IcedTea, IBM Corporation, Snap Build, PureOS ### [testimonials.md](/testimonials/) Testimonials What people have said about JBang! So much fun, thanks for this. You may just have saved me from using python to teach my 12 year old to program. Rafael Chaves @modelprogrammer June 6, 2021 Yeah, calling it now: #jbang is the killer Java CLI you were waiting for. Andres Almiray @aalmiray June 3, 2021 It seems @jbangdev will be the one true source for all things Java soon enough! Well done :) Ingo @lasombra_br January 12, 2021 I could not imagine this is now possible in Java. Thanks, jbang for this wonderful project. You're simply amazing! 😊 Julian Jupiter @julianvjupiter September 26, 2020 The combination of @picocli and @jbangdev seems to be a perfect match. Java scripting raised to a new level. Oliver Libutzki @OliverLibutzki August 16, 2020 With @jbangdev you invoke a script (even url) written in #java (it solves the compiling and dependencies for you). It's now easy to consider Java for cli tools, daily tasks/scripts and share them. Roy Golan @_royg_ September 6, 2020 Just tried out @jbangdev for a PoC at work, a complete game changer, opens up a whole range of possibilities for scripting with Java. Joe @HackerMonkey3 August 28, 2021 Jbang is the best thing since sliced bread! Daniel @dannemano October 8, 2021 Yup. Imma be writing all my shell scripts in Java thanks to @jbangdev and @picocli. Can't even begin to imagine what I can do with the might of the entire Java ecosystem. Seeraj Muneer @gh_geek November 1, 2021 @jbangdev just blew my mind. It definitely brings Java to another level. I would argue that Java could be a better choice than Go for writing scripts/CLIs with @jbangdev Gimi Liang @lzhm February 10, 2022 When I look at @maxandersen wordle solver with @jbangdev I want immediately unlearn Python 😎 Patrick Baumgartner @patbaumgartner January 27, 2022 JBang (@jbangdev) is seriously bringing back the joy of creating CLIs and tooling in Java! I've spent the last few years writing those exclusively in Go, but JBang makes this an almost analogous experience on the JVM front. Great work, @maxandersen! Preslav Rachev @preslavrachev September 1, 2022 Single file Java applications with @jbangdev. JBang is mindblowing. I use it when I need to quickly try out a library and don't want to waste any time. Strong recommendation! Maciej Walkowiak @maciejwalkowiak November 2, 2022 .testimonials-grid { max-width: 900px; margin: 2rem auto; } .testimonial-card { border: 1px solid #e1e4e8; border-radius: 12px; padding: 1.5rem; margin-bottom: 1.5rem; background: #fff; } .testimonial-text { margin: 0 0 1rem 0; padding: 0; border: none; font-size: 1.05rem; line-height: 1.6; color: #1a1a1a; } .testimonial-text p { margin: 0; white-space: pre-line; } .testimonial-meta { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.5rem; font-size: 0.9rem; color: #555; } .testimonial-handle a { color: #eb586f; text-decoration: none; } .testimonial-handle a:hover { text-decoration: underline; } .testimonial-date { color: #999; } .testimonial-date::before { content: "·"; margin-right: 0.5rem; } ### [Try JBang](/try/) Try JBang Below is an embedded editor where you can write Java code using JBang directives, click run and see it execute. Note: This JBang try page is relying on a free public Binder service (MyBinder) and is subject to usage limits and temporary availability issues. For more details, see the Binder user guidelines. Use a dedicated IDE if these constraints does not work for you. { bootstrap: true, requestKernel: true, persistKernel: false, useJupyterLite: false, useBinder: true, binderOptions: { repo: "jupyter-java/jupyter-java-binder", ref: "jbang", binderUrl: 'https://mybinder.org' }, kernelOptions: { kernelName: "jbang", }, codeMirrorConfig: { theme: "default", lineNumbers: true, lineWrapping: true } } /* Set Jupyter/Thebe code font size for try page */ :root { --jp-code-font-size: 20px; } /* Editor controls - buttons and status in one row */ .editor-controls { display: flex; align-items: center; gap: 1rem; margin: 0; padding: 0.5rem 0.75rem; background: #f8f9fa; border: 1px solid #dee2e6; border-radius: 4px; } .editor-controls .thebe-status { margin: 0; padding: 0; flex: 1; border: none !important; background: transparent !important; font-size: 0.85rem; } /* Make all status elements inline */ .thebe-status-stub, .thebe-status-field, .thebe-status-message { display: inline; margin: 0 0.25rem; font-size: 0.85rem; font-style: normal; } .thebe-status-message { color: #999; font-size: 0.8rem; } /* Hide unwanted Thebe buttons */ .thebe-restartall-button, .thebe-runall-button { display: none !important; } /* Reduce spacing around editor */ .listingblock { margin: 0 0 1.5rem 0; } .listingblock .content { margin: 0; } .listingblock pre { margin: 0; } .thebelab-cell { margin: 0 !important; } .try-options { display: grid; grid-template-columns: 1fr 1fr; gap: 2rem; margin: 2rem 0; } .try-option { border: 2px solid #e1e5e9; border-radius: 12px; padding: 1.5rem; transition: all 0.3s ease; background: #fff; } .try-option:hover { border-color: #007acc; box-shadow: 0 4px 12px rgba(0, 122, 204, 0.1); transform: translateY(-2px); } .try-option.jupyter { border-color: #f37626; } .try-option.jupyter:hover { border-color: #f37626; box-shadow: 0 4px 12px rgba(243, 118, 38, 0.1); } .try-option-header h3 { margin: 0 0 0.5rem 0; font-size: 1.5rem; color: #333; } .try-option-header p { margin: 0 0 1rem 0; color: #666; font-size: 1rem; } .try-option-content ul { margin: 1rem 0; padding-left: 1.5rem; } .try-option-content li { margin: 0.5rem 0; color: #555; } .try-option-actions { margin-top: 1.5rem; display: flex; gap: 1rem; flex-wrap: wrap; } /* Use Minimal Mistakes button styles - no custom button CSS needed */ @media (max-width: 768px) { .try-options { grid-template-columns: 1fr; gap: 1rem; } .try-option-actions { flex-direction: column; } .btn { width: 100%; } } // Handle URL parameters for pre-filling and redirects document.addEventListener('DOMContentLoaded', function() { const urlParams = new URLSearchParams(window.location.search); const repoParam = urlParams.get('repo'); const branchParam = urlParams.get('branch'); const filepathParam = urlParams.get('filepath'); const codeParam = urlParams.get('code'); const redirectParam = urlParams.get('redirect'); // Update Jupyter link with parameters if provided if (repoParam) { const jupyterLinkBtn = document.querySelector('.try-option.jupyter .btn--primary'); if (jupyterLinkBtn) { // Build MyBinder URL const base = 'https://mybinder.org/v2/gh/jupyter-java/jupyter-java-binder/jbang'; const content = 'content'; const branch = branchParam || 'main'; const filepath = filepathParam || ''; const pathToOpen = filepath ? ('lab/tree/' + content + '/' + filepath) : ('lab/tree/' + content + '/'); const gitPullParams = new URLSearchParams(); gitPullParams.append('repo', repoParam); gitPullParams.append('urlpath', pathToOpen); gitPullParams.append('branch', branch); gitPullParams.append('targetPath', content); const gitPullUrlpath = 'git-pull?' + gitPullParams.toString(); const encodedNestedUrlpath = encodeURIComponent(gitPullUrlpath); const jupyterUrl = base + '?urlpath=' + encodedNestedUrlpath; jupyterLinkBtn.href = jupyterUrl; // Update button text with title or repo URL const titleParam = urlParams.get('title'); if (titleParam) { jupyterLinkBtn.textContent = '🚀 Open ' + titleParam; } else { // Extract a friendly name from the repo URL let repoName = repoParam; try { const urlObj = new URL(repoParam); const pathParts = urlObj.pathname.split('/').filter(p => p); if (pathParts.length >= 2) { repoName = pathParts[pathParts.length - 2] + '/' + pathParts[pathParts.length - 1]; } } catch (e) { // Keep full URL if parsing fails } jupyterLinkBtn.textContent = '🚀 Open ' + repoName; } } } // Insert code into editor if provided if (codeParam) { try { const decodedCode = decodeURIComponent(codeParam); // Method 1: Try to set it immediately before Thebe loads const codeBlock = document.querySelector('[data-executable="true"]'); if (codeBlock) { codeBlock.textContent = decodedCode; } // Method 2: Also try after Thebe loads by monitoring the CodeMirror instance const checkEditor = setInterval(function() { const codeBlock = document.querySelector('[data-executable="true"]'); const codeMirror = document.querySelector('.CodeMirror'); if (codeMirror && codeMirror.CodeMirror) { // CodeMirror is loaded, update it directly codeMirror.CodeMirror.setValue(decodedCode); clearInterval(checkEditor); } else if (codeBlock && !codeMirror) { // Thebe hasn't initialized yet, keep updating the original element codeBlock.textContent = decodedCode; } }, 100); // Stop checking after 10 seconds setTimeout(() => clearInterval(checkEditor), 10000); } catch (e) { console.error('Failed to decode code parameter:', e); } } // Handle redirect if specified if (redirectParam !== null) { const redirectDelay = redirectParam === '' ? 0 : parseInt(redirectParam, 10) || 0; const jupyterLink = document.querySelector('.try-option.jupyter .btn--primary'); if (jupyterLink) { const targetUrl = jupyterLink.href; if (redirectDelay > 0) { // Show countdown message const messageDiv = document.createElement('div'); messageDiv.style.cssText = 'position: fixed; top: 20px; left: 50%; transform: translateX(-50%); background: #f37626; color: white; padding: 1rem 2rem; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); z-index: 9999; font-size: 1.1rem;'; messageDiv.innerHTML = `Redirecting to Jupyter in ${redirectDelay} seconds...`; document.body.appendChild(messageDiv); let remaining = redirectDelay; const countdownEl = document.getElementById('countdown'); const countdownInterval = setInterval(() => { remaining--; if (countdownEl) { countdownEl.textContent = remaining; } if (remaining -- Select an example -- ▼ Hello WorldJava 25 Hello JBang (with ASCII art)Java 25 Language Features ▶ RecordsJava 21 Pattern Matching in SwitchJava 25 Sequenced CollectionsJava 21 Text Blocks & Word CountJava 15 Concurrency ▶ Virtual Threads (10,000 tasks)Java 21 Data Processing ▶ CSV Parsing (no deps)Java 25 HTTP Client + JacksonJava 21 Qute - Template EngineJava 25 Shell Integration ▶ Jash - Shell Commands in JavaJava 25 💡 Contribute samples document.addEventListener('DOMContentLoaded', function() { const dropdown = document.querySelector('.custom-dropdown'); const toggle = document.querySelector('.dropdown-toggle'); const menu = document.querySelector('.dropdown-menu'); const dropdownText = document.querySelector('.dropdown-text'); function loadSampleCode(code) { console.log('Loading code, length:', code.length); // Try to find CodeMirror instance const cmElements = document.querySelectorAll('.CodeMirror'); console.log('Found CodeMirror elements:', cmElements.length); let loaded = false; cmElements.forEach(cm => { if (cm.CodeMirror) { console.log('Setting value in CodeMirror instance'); cm.CodeMirror.setValue(code); loaded = true; } }); // Also update the original code element as fallback const codeElement = document.querySelector('code[data-executable="true"]'); if (codeElement) { console.log('Updating original code element'); codeElement.textContent = code; } if (!loaded) { console.warn('CodeMirror not found, code updated in underlying element only'); } } const searchInput = document.querySelector('.search-input'); let highlightedIndex = -1; // Get all visible menu items (not categories) function getVisibleMenuItems() { return Array.from(menu.querySelectorAll('.menu-item')) .filter(item => item.style.display !== 'none' && item.dataset.url); } // Highlight a menu item function highlightItem(index) { const items = getVisibleMenuItems(); // Remove previous highlight items.forEach(item => item.classList.remove('highlighted')); // Add new highlight if (index >= 0 && index { console.log('Response status:', response.status); if (!response.ok) throw new Error('HTTP ' + response.status); return response.text(); }) .then(code => { console.log('Code received, loading...'); loadSampleCode(code); }) .catch(error => { console.error('Error loading sample:', error); alert('Failed to load sample from ' + url + ': ' + error.message); }); } // Toggle dropdown toggle.addEventListener('click', function(e) { e.stopPropagation(); dropdown.classList.toggle('open'); // Focus search input when opening if (dropdown.classList.contains('open')) { setTimeout(() => searchInput.focus(), 100); } else { highlightedIndex = -1; } }); // Handle category clicks to expand/collapse const categories = menu.querySelectorAll('.menu-category[data-category]'); categories.forEach(category => { category.addEventListener('click', function(e) { e.stopPropagation(); this.classList.toggle('expanded'); }); }); // Search/filter functionality searchInput.addEventListener('input', function(e) { const searchTerm = this.value.toLowerCase().trim(); // Reset highlight highlightedIndex = -1; // Get all categories and their content containers const allCategories = menu.querySelectorAll('.menu-category[data-category]'); const allCategoryContents = menu.querySelectorAll('.category-content'); const allMenuItems = menu.querySelectorAll('.menu-item'); // Remove all highlights allMenuItems.forEach(item => item.classList.remove('highlighted')); if (searchTerm === '') { // Reset: hide all category contents, show all items allCategoryContents.forEach(content => { const category = content.previousElementSibling; if (category && category.classList.contains('menu-category')) { category.classList.remove('expanded'); } }); allCategories.forEach(cat => cat.style.display = ''); allCategoryContents.forEach(content => content.style.display = ''); allMenuItems.forEach(item => item.style.display = ''); return; } // Filter items based on search term allMenuItems.forEach(item => { const label = item.querySelector('.menu-label'); if (label) { const text = label.textContent.toLowerCase(); if (text.includes(searchTerm)) { item.style.display = ''; } else { item.style.display = 'none'; } } }); // Handle categories: show/hide based on whether they have visible items allCategories.forEach((category, index) => { const categoryContent = category.nextElementSibling; if (categoryContent && categoryContent.classList.contains('category-content')) { const visibleItems = Array.from(categoryContent.querySelectorAll('.menu-item')) .filter(item => item.style.display !== 'none'); if (visibleItems.length > 0) { // Expand category and show it category.style.display = ''; category.classList.add('expanded'); categoryContent.style.display = ''; } else { // Hide category if no matching items category.style.display = 'none'; categoryContent.style.display = 'none'; } } }); // Auto-highlight first item if only one match const visibleItems = getVisibleMenuItems(); if (visibleItems.length === 1) { highlightItem(0); } }); // Prevent search input click from closing dropdown searchInput.addEventListener('click', function(e) { e.stopPropagation(); }); // Keyboard navigation searchInput.addEventListener('keydown', function(e) { const items = getVisibleMenuItems(); switch(e.key) { case 'ArrowDown': e.preventDefault(); if (highlightedIndex 0) { highlightItem(highlightedIndex - 1); } else { highlightItem(items.length - 1); // Wrap to last } break; case 'Enter': e.preventDefault(); // If there's only one visible item, select it if (items.length === 1) { selectItem(items[0]); } // If an item is highlighted, select it else if (highlightedIndex >= 0 && highlightedIndex 0) { selectItem(items[0]); } break; case 'Escape': e.preventDefault(); dropdown.classList.remove('open'); searchInput.value = ''; highlightedIndex = -1; searchInput.dispatchEvent(new Event('input')); break; } }); // Close dropdown when clicking outside document.addEventListener('click', function() { dropdown.classList.remove('open'); }); // Handle menu item clicks menu.addEventListener('click', function(e) { const menuItem = e.target.closest('.menu-item'); if (!menuItem) return; const url = menuItem.dataset.url; const label = menuItem.querySelector('.menu-label').textContent; // Update dropdown text dropdownText.textContent = label; // Close dropdown dropdown.classList.remove('open'); console.log('Fetching:', url); // Fetch the sample code fetch(url) .then(response => { console.log('Response status:', response.status); if (!response.ok) throw new Error('HTTP ' + response.status); return response.text(); }) .then(code => { console.log('Code received, loading...'); loadSampleCode(code); }) .catch(error => { console.error('Error loading sample:', error); alert('Failed to load sample from ' + url + ': ' + error.message); }); }); }); .try-samples-selector { margin: 0; display: flex; align-items: center; gap: 0.75rem; } .try-samples-selector label { font-weight: 600; margin: 0; font-size: 0.9rem; color: #555; } .contribute-link { font-size: 0.85rem; color: #6f777d; text-decoration: none; padding: 0.4rem 0.75rem; border: 1px solid #e1e5e9; border-radius: 6px; transition: all 0.2s ease; white-space: nowrap; } .contribute-link:hover { color: #f37626; border-color: #f37626; text-decoration: none; background: rgba(243, 118, 38, 0.05); } /* Custom dropdown */ .custom-dropdown { position: relative; display: inline-block; } .dropdown-toggle { display: flex; align-items: center; justify-content: space-between; min-width: 200px; padding: 0.4rem 0.6rem; font-size: 0.85rem; border: 1px solid #ddd; border-radius: 6px; background: white; cursor: pointer; transition: border-color 0.2s ease; } .dropdown-toggle:hover { border-color: #999; } .dropdown-arrow { font-size: 0.7rem; transition: transform 0.2s ease; } .custom-dropdown.open .dropdown-arrow { transform: rotate(180deg); } /* Dropdown menu */ .dropdown-menu { position: absolute; top: 100%; left: 0; right: 0; background: white; border: 1px solid #ddd; border-radius: 6px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); max-height: 400px; overflow-y: auto; z-index: 1000; display: none; margin-top: 2px; } .custom-dropdown.open .dropdown-menu { display: block; } /* Search input */ .menu-search { position: sticky; top: 0; background: white; padding: 0.5rem; border-bottom: 1px solid #e0e0e0; z-index: 10; } .search-input { width: 100%; padding: 0.5rem 0.75rem; border: 1px solid #ddd; border-radius: 4px; font-size: 0.85rem; outline: none; transition: border-color 0.2s ease; } .search-input:focus { border-color: #999; box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.05); } /* Menu categories */ .menu-category { padding: 0.6rem 0.75rem; font-size: 0.75rem; font-weight: 700; color: #444; background: #f0f0f0; text-transform: uppercase; letter-spacing: 0.5px; margin-top: 0.25rem; cursor: pointer; display: flex; align-items: center; justify-content: space-between; transition: background-color 0.15s ease; } .menu-category:hover { background-color: #e8e8e8; } .menu-category:first-child { margin-top: 0; } .category-meta { display: flex; align-items: center; gap: 0.5rem; } .category-arrow { font-size: 0.6rem; transition: transform 0.2s ease; display: inline-block; } .menu-category.expanded .category-arrow { transform: rotate(90deg); } .category-badge { background: #dee2e6; color: #495057; padding: 0.15rem 0.5rem; border-radius: 12px; font-size: 0.65rem; font-weight: 600; text-transform: none; } .category-content { display: none; } .menu-category.expanded + .category-content { display: block; } /* Menu groups */ .menu-group { background: #fafafa; } .group-label { padding: 0.5rem 0.75rem 0.3rem 1rem; font-size: 0.7rem; font-weight: 600; color: #666; background: #f5f5f5; cursor: default; text-transform: uppercase; letter-spacing: 0.3px; } /* Menu items */ .menu-item { display: flex; align-items: center; justify-content: space-between; padding: 0.6rem 0.75rem; cursor: pointer; font-size: 0.85rem; transition: background-color 0.15s ease; border-bottom: 1px solid #f8f8f8; } .menu-item:hover { background-color: #e8f4f8; } .menu-item.highlighted { background-color: #d0e8f2; border-left: 3px solid #007acc; padding-left: calc(0.75rem - 3px); } .menu-subitem.highlighted { padding-left: calc(2rem - 3px); } .menu-group .menu-subitem.highlighted { padding-left: calc(2.5rem - 3px); } .menu-item:last-child { border-bottom: none; } .menu-subitem { padding-left: 2rem; font-size: 0.82rem; } .menu-group .menu-subitem { padding-left: 2.5rem; font-size: 0.8rem; } .menu-badge { background: #e9ecef; color: #666; padding: 0.1rem 0.4rem; border-radius: 10px; font-size: 0.7rem; margin-left: 0.5rem; } @media (max-width: 768px) { .try-samples-selector { flex-direction: column; align-items: stretch; gap: 0.5rem; } .dropdown-toggle { width: 100%; min-width: auto; } } ///usr/bin/env jbang "$0" "$@" ; exit $? //DEPS com.github.lalyos:jfiglet:0.0.8 import com.github.lalyos.jfiglet.FigletFont; IO.println(FigletFont.convertOneLine("Hello JBang")); You can also open a full interactive development environment. 🚀 JBang powered Jupyter Environment Interactive development environment with rich output and data visualization. Uses MyBinder. Experience JBang in a full Jupyter notebook environment with: Run .java, .jsh, and more using JBang Interactive code execution Rich output and visualizations Multiple files Use your own github or gist Launch Jupyter Environment Create Custom Link ### [Create Custom JBang Try Link](/try/custom/) Create Custom JBang Try Link Create a shareable link to the JBang try page with your own custom code and/or link to GitHub repository or Gist. Link Generator Create a custom shareable link to /try/ with pre-filled repository, code, or auto-redirect: Java Code (optional): Pre-fill the code editor with this code Repository URL (optional): GitHub repository or Gist URL for Jupyter environment Repository Title (optional): Friendly name for the repository (shown in button text) Branch (optional): Leave empty for default branch File/Path (optional): Specific file or directory to open in Jupyter Auto-Redirect (optional): Seconds to wait before redirecting to Jupyter (0 for immediate, empty for no redirect) Generated Try Link: 📋 Copy 🚀 Open Try Page How it Works All fields are optional! The link updates in real-time as you type: Code: Java code to pre-fill in the try page editor Repository: GitHub repository or Gist URL - updates the Jupyter launch link on /try/ Title: Friendly name shown in the "Launch" button (e.g., "My Project" → "🚀 Open My Project") Branch: Specific branch to use (defaults to main) File Path: Specific file or directory to open in Jupyter Redirect: Auto-redirect to Jupyter after specified seconds (empty = no redirect) Result: Generates a shareable /try/ link - defaults to /try/ if no fields are filled Generated Link Format This tool generates links to /try/ with the following format: /try/?repo=REPO_URL&title=TITLE&branch=BRANCH&filepath=PATH&code=CODE&redirect=SECONDS Parameters: code (optional): URL-encoded Java code to pre-fill the editor repo (optional): GitHub repository or Gist URL Example: https://github.com/username/repo Example: https://gist.github.com/username/gist-id title (optional): Friendly name for the repository shown in button Example: My Awesome Project → button shows "🚀 Open My Awesome Project" branch (optional): Branch name (defaults to main) filepath (optional): Path to specific file or directory redirect (optional): Seconds to wait before auto-redirecting to Jupyter Empty value or 0 = immediate redirect Omit parameter = no redirect Example Generated Links: Pre-fill code: /try/?code=IO.println(%22Hello%20JBang!%22); Repository with redirect: /try/?repo=https://github.com/jbangdev/jbang-jupyter-examples&redirect=3 Gist with code: /try/?repo=https://gist.github.com/user/abc123&code=IO.println(%22Demo%22); Examples GitHub Repository https://github.com/username/my-java-notebooks Opens the repository in Jupyter with JBang kernel GitHub Gist https://gist.github.com/username/abc123def456 Opens the Gist as a Jupyter environment Specific File https://github.com/username/repo + path: notebooks/demo.ipynb Opens a specific notebook file directly /* Use Minimal Mistakes button styles - no custom button CSS needed */ .preset-buttons { display: flex; gap: 1rem; margin: 2rem 0; flex-wrap: wrap; } .preset-buttons .btn { flex: 1; min-width: 200px; text-align: center; } .link-generator { background: #f8f9fa; border: 1px solid #dee2e6; border-radius: 8px; padding: 2rem; margin: 2rem 0; } .generator-form { display: grid; gap: 1rem; } .form-group { display: flex; flex-direction: column; } .form-group label { font-weight: 600; margin-bottom: 0.5rem; color: #333; } .form-group input, .form-group textarea { padding: 0.75rem; border: 1px solid #ced4da; border-radius: 4px; font-size: 1rem; font-family: inherit; } .form-group textarea { font-family: monospace; resize: vertical; } .form-group input:focus, .form-group textarea:focus { outline: none; border-color: #f37626; box-shadow: 0 0 0 2px rgba(243, 118, 38, 0.25); } .form-help { color: #6c757d; font-size: 0.875rem; margin-top: 0.25rem; } .generated-link { margin-top: 2rem; padding-top: 2rem; border-top: 1px solid #dee2e6; } .generated-link h4 { margin: 0 0 1rem 0; color: #333; } .link-container { display: flex; gap: 0.5rem; margin: 1rem 0; } .link-container input { flex: 1; padding: 0.75rem; border: 1px solid #ced4da; border-radius: 4px; background: white; font-family: monospace; font-size: 0.9rem; } .examples { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 1.5rem; margin: 2rem 0; } .example { background: white; border: 1px solid #dee2e6; border-radius: 8px; padding: 1.5rem; } .example h4 { margin: 0 0 1rem 0; color: #f37626; } .example code { background: #f8f9fa; padding: 0.5rem; border-radius: 4px; font-family: monospace; display: block; margin: 0.5rem 0; word-break: break-all; } .example p { margin: 0.5rem 0 0 0; color: #666; font-size: 0.9rem; } @media (max-width: 768px) { .link-container { flex-direction: column; } .examples { grid-template-columns: 1fr; } } document.addEventListener('DOMContentLoaded', function() { const repoUrlInput = document.getElementById('repo-url'); const titleInput = document.getElementById('title'); const branchInput = document.getElementById('branch'); const filepathInput = document.getElementById('filepath'); const codeTextarea = document.getElementById('code'); const redirectInput = document.getElementById('redirect'); const customLinkInput = document.getElementById('custom-link'); const launchLink = document.getElementById('launch-link'); const copyBtn = document.getElementById('copy-link'); // Read URL parameters and pre-fill form const urlParams = new URLSearchParams(window.location.search); const repoParam = urlParams.get('repo'); const titleParam = urlParams.get('title'); const branchParam = urlParams.get('branch'); const filepathParam = urlParams.get('filepath'); const codeParam = urlParams.get('code'); const redirectParam = urlParams.get('redirect'); if (repoParam) { repoUrlInput.value = repoParam; } if (titleParam) { titleInput.value = titleParam; } if (branchParam) { branchInput.value = branchParam; } if (filepathParam) { filepathInput.value = filepathParam; } if (codeParam) { codeTextarea.value = decodeURIComponent(codeParam); } if (redirectParam !== null) { redirectInput.value = redirectParam; } // Function to generate the link function generateLink() { const repoUrl = repoUrlInput.value.trim(); const title = titleInput.value.trim(); const branch = branchInput.value.trim(); const filepath = filepathInput.value.trim(); const code = codeTextarea.value.trim(); const redirect = redirectInput.value.trim(); // Build /try/ URL with parameters const tryParams = new URLSearchParams(); if (repoUrl) { tryParams.append('repo', repoUrl); } if (title) { tryParams.append('title', title); } if (branch) { tryParams.append('branch', branch); } if (filepath) { tryParams.append('filepath', filepath); } if (code) { tryParams.append('code', code); } if (redirect !== '') { tryParams.append('redirect', redirect); } // Build the final URL - default to /try/ if no parameters const queryString = tryParams.toString(); const relativePath = queryString ? '/try/?' + queryString : '/try/'; // Build full URL with host for copying const fullUrl = window.location.origin + relativePath; // Update the link display (show full URL) and href (relative path) customLinkInput.value = fullUrl; launchLink.href = relativePath; } // Generate link on any input change repoUrlInput.addEventListener('input', generateLink); titleInput.addEventListener('input', generateLink); branchInput.addEventListener('input', generateLink); filepathInput.addEventListener('input', generateLink); codeTextarea.addEventListener('input', generateLink); redirectInput.addEventListener('input', generateLink); // Generate initial link generateLink(); copyBtn.addEventListener('click', function() { customLinkInput.select(); navigator.clipboard.writeText(customLinkInput.value) .then(() => { // Visual feedback const originalText = copyBtn.textContent; copyBtn.textContent = '✅ Copied!'; setTimeout(() => { copyBtn.textContent = originalText; }, 2000); }) .catch(() => { // Fallback or error feedback const originalText = copyBtn.textContent; copyBtn.textContent = '❌ Copy failed'; setTimeout(() => { copyBtn.textContent = originalText; }, 2000); }); }); }); ### [Try JBang with JDoodle](/try/jdoodle/) Try JBang with JDoodle Below is a JDoodle window which provides a simplistic Java development environment. Run hello.java by clicking the Execute button. ///usr/bin/env jbang "$0" "$@" ; exit $? //DEPS com.github.lalyos:jfiglet:0.0.8 import com.github.lalyos.jfiglet.FigletFont; class hello { public static void main(String... args) throws Exception { System.out.println(FigletFont.convertOneLine( "Hello " + ((args.length>0)?args[0]:"jbang"))); ;; } } ### [jbang twitter account](/twitter/) Tweet This! Twitter suspended @jbangdev and it took 60 days to get reactivated! I created this landing page for Twitter, as Twitter decided to suspend @jbangdev before it even made a single tweet, follow or like. Luckily, they eventually fixed it, and @jbangdev is now live. Below is a timeline on the documented events - if you want to give kudos to Twitter do a tweet using this link. Timeline March 4th, 2020: I created @jbangdev twitter account when I knew I could get the domain jbang.dev. No tweets made as I needed to get the site up and running first. May 22nd, 2020: I requested and completed a password reset. Same day I realized the account had been suspended. No email sent to my email on this. Submitted an appeal and asked to confirm email. May 24nd, 2020: I confirmed the email on support case 0156196710. No response. May 29th, 2020: jbang.dev launched with refereces to @jbangdev in hope it would be fixed soon. June 14h, 2020: I confirm email again asking for updates. No response. July 5th, 2020: I open new case, gets email stating its same as case 0156196710 with comment "We’ll continue our review with this information." July 9th, 2020: Got tired of waiting thus putting this page up. July 21st, 2020: Twitter reactivated the account after a ping from Red Hat social media contacts (Thank you!) ### [JBang Usage](/usage/) JBangUsage Below is a visualization of recent version checks done by JBang users the last 90 days. You can find some other fun stats in the Leaderboards and GitHub download stats. 6 Continents 140 Countries 201 Timezones 4291 Cities 1244092 Version Checks Stop Spin body { margin: 0; } .menu-item { cursor: pointer; -webkit-transition: all 0.1s ease-out; } .menu-item:hover, .menu-item.active { font-size: 23px; } .counters { display: flex; gap: 30px; background-color: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); } .counter { text-align: center; } .number { font-size: 2.5em; font-weight: bold; color: #4a90e2; } .label { display: block; margin-top: 8px; font-size: 1em; color: #666; text-transform: uppercase; } document.addEventListener("DOMContentLoaded", function () { const counters = document.querySelectorAll(".number"); counters.forEach(counter => { counter.innerText = '0'; const updateCounter = () => { const target = +counter.getAttribute('data-target'); const count = +counter.innerText; const increment = target / 200; if (count d.sumWeight * 0.002) .hexBinResolution(4) .hexTopColor(d => weightColor(d.sumWeight)) .hexSideColor(d => weightColor(d.sumWeight)) .hexBinMerge(true) .hexLabel(() => `BLAH!`) .enablePointerInteraction(true); // performance improvement fetch('/assets/data/jbang-versionchecks.csv').then(res => res.text()) .then(csv => d3.csvParse(csv, ({lat, lng, count}) => ({lat: +lat, lng: +lng, count: +count}))) .then(data => world.hexBinPointsData(data)); // Add auto-rotation world.controls().autoRotate = true; world.controls().autoRotateSpeed = 1.5; document.getElementById("autorotate").onclick = function (e) { world.controls().autoRotate = !world.controls().autoRotate; e.target.textContent = world.controls().autoRotate ? "Stop Spin" : "Start Spin"; }; ### [Use Cases](/use-cases/) How Developers Use JBang JBang transforms how developers work with Java across different skill levels and use cases. 🎓 For Learning & Education Java Fundamentals Perfect for students and beginners learning Java: public class LearningBasics { public static void main(String[] args) { // Variables and data types String name = "Java Learner"; int age = 25; double height = 1.75; System.out.println("Hello, " + name + "!"); System.out.println("Age: " + age + ", Height: " + height + "m"); // Control structures if (age >= 18) { System.out.println("You're an adult!"); } else { System.out.println("You're a minor."); } // Loops for (int i = 1; i <= 5; i++) { System.out.println("Count: " + i); } } } Testing with JUnit Learn testing without complex setup: //DEPS org.junit.jupiter:junit-jupiter:5.9.2 import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; public class CalculatorTest { @Test public void testAddition() { assertEquals(4, 2 + 2); } @Test public void testMultiplication() { assertEquals(10, 2 * 5); } public static void main(String[] args) { // Run tests automatically org.junit.platform.launcher.LauncherFactory .create() .execute(org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder .request() .selectors(org.junit.platform.engine.discovery.DiscoverySelectors .selectClass(CalculatorTest.class)) .build()); } } 🚀 For Rapid Prototyping Quick Web APIs Build REST APIs in minutes: //DEPS org.springframework.boot:spring-boot-starter-web:3.2.0 //DEPS org.springframework.boot:spring-boot-starter-data-jpa:3.2.0 //DEPS com.h2database:h2:2.1.214 import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.web.bind.annotation.*; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import jakarta.persistence.*; @SpringBootApplication @EnableJpaRepositories @RestController public class QuickAPI { @Entity public static class Task { @Id @GeneratedValue public Long id; public String title; public boolean completed; } public interface TaskRepository extends JpaRepository<Task, Long> {} @Autowired TaskRepository tasks; @GetMapping("/tasks") public List<Task> getTasks() { return tasks.findAll(); } @PostMapping("/tasks") public Task createTask(@RequestBody Task task) { return tasks.save(task); } public static void main(String[] args) { SpringApplication.run(QuickAPI.class, args); } } Data Processing Scripts Process data with powerful libraries: //DEPS org.apache.commons:commons-csv:1.10.0 //DEPS com.fasterxml.jackson.core:jackson-databind:2.15.2 import org.apache.commons.csv.*; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.*; import java.util.*; public class DataProcessor { public static void main(String[] args) throws Exception { // Read CSV Reader reader = new FileReader("data.csv"); CSVParser parser = CSVFormat.DEFAULT.parse(reader); List<Map<String, String>> records = new ArrayList<>(); for (CSVRecord record : parser) { Map<String, String> row = new HashMap<>(); for (String header : parser.getHeaderNames()) { row.put(header, record.get(header)); } records.add(row); } // Process and output JSON ObjectMapper mapper = new ObjectMapper(); mapper.writerWithDefaultPrettyPrinter() .writeValue(new File("output.json"), records); System.out.println("Processed " + records.size() + " records"); } } 🔧 For DevOps & Automation System Administration Automate system tasks with Java: //DEPS org.apache.commons:commons-io:2.11.0 //DEPS com.fasterxml.jackson.core:jackson-databind:2.15.2 import org.apache.commons.io.FileUtils; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.*; import java.nio.file.*; import java.time.LocalDateTime; public class SystemMonitor { public static void main(String[] args) throws Exception { // Check disk space File root = new File("/"); long freeSpace = root.getFreeSpace(); long totalSpace = root.getTotalSpace(); double usagePercent = ((double)(totalSpace - freeSpace) / totalSpace) * 100; // Check memory Runtime runtime = Runtime.getRuntime(); long maxMemory = runtime.maxMemory(); long usedMemory = runtime.totalMemory() - runtime.freeMemory(); double memoryUsage = ((double) usedMemory / maxMemory) * 100; // Create report Map<String, Object> report = Map.of( "timestamp", LocalDateTime.now().toString(), "disk_usage_percent", Math.round(usagePercent * 100.0) / 100.0, "memory_usage_percent", Math.round(memoryUsage * 100.0) / 100.0, "free_disk_gb", freeSpace / (1024 * 1024 * 1024) ); ObjectMapper mapper = new ObjectMapper(); mapper.writerWithDefaultPrettyPrinter() .writeValue(new File("system_report.json"), report); System.out.println("System report generated: system_report.json"); } } CI/CD Scripts Integrate with build pipelines: //DEPS org.apache.httpcomponents.client5:httpclient5:5.2.1 //DEPS com.fasterxml.jackson.core:jackson-databind:2.15.2 import org.apache.hc.client5.http.classic.methods.*; import org.apache.hc.client5.http.impl.classic.*; import com.fasterxml.jackson.databind.ObjectMapper; import java.util.*; public class DeploymentChecker { public static void main(String[] args) throws Exception { String appUrl = args.length > 0 ? args[0] : "http://localhost:8080/health"; try (CloseableHttpClient client = HttpClients.createDefault()) { HttpGet request = new HttpGet(appUrl); int statusCode = client.execute(request, response -> { String body = new String(response.getEntity().getContent().readAllBytes()); System.out.println("Status: " + response.getCode()); System.out.println("Response: " + body); return response.getCode(); }); if (statusCode == 200) { System.out.println("✅ Application is healthy"); System.exit(0); } else { System.out.println("❌ Application health check failed"); System.exit(1); } } } } 🎨 For Creative Projects Generative Art Create art with Java: //DEPS org.apache.batik:batik-svggen:1.14 //DEPS org.apache.batik:batik-dom:1.14 import org.apache.batik.svggen.SVGGraphics2D; import org.apache.batik.dom.GenericDOMImplementation; import org.w3c.dom.Document; import java.awt.*; import java.io.*; public class GenerativeArt { public static void main(String[] args) throws Exception { // Create SVG document Document doc = GenericDOMImplementation.getDOMImplementation() .createDocument("http://www.w3.org/2000/svg", "svg", null); SVGGraphics2D g2d = new SVGGraphics2D(doc); g2d.setSVGCanvasSize(new Dimension(800, 600)); // Draw generative art g2d.setColor(Color.BLACK); g2d.fillRect(0, 0, 800, 600); for (int i = 0; i < 100; i++) { int x = (int)(Math.random() * 800); int y = (int)(Math.random() * 600); int size = (int)(Math.random() * 50) + 10; g2d.setColor(new Color( (int)(Math.random() * 255), (int)(Math.random() * 255), (int)(Math.random() * 255) )); g2d.fillOval(x, y, size, size); } // Save SVG g2d.stream(new FileWriter("art.svg"), true); System.out.println("Generated art.svg"); } } 🏢 For Enterprise Development Microservices Build lightweight services: //DEPS io.quarkus:quarkus-resteasy-reactive:3.2.0.Final //DEPS io.quarkus:quarkus-hibernate-orm-panache:3.2.0.Final //DEPS io.quarkus:quarkus-jdbc-postgresql:3.2.0.Final //DEPS io.quarkus:quarkus-smallrye-health:3.2.0.Final import io.quarkus.runtime.Quarkus; import io.quarkus.runtime.annotations.QuarkusMain; import jakarta.ws.rs.*; import jakarta.ws.rs.core.MediaType; import org.eclipse.microprofile.health.HealthCheck; import org.eclipse.microprofile.health.HealthCheckResponse; import org.eclipse.microprofile.health.Readiness; @QuarkusMain public class Microservice { public static void main(String[] args) { Quarkus.run(args); } } @Path("/api") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public class ApiResource { @GET @Path("/health") public Map<String, String> health() { return Map.of("status", "healthy", "timestamp", java.time.Instant.now().toString()); } @GET @Path("/data") public List<Map<String, Object>> getData() { // Your business logic here return List.of( Map.of("id", 1, "name", "Item 1"), Map.of("id", 2, "name", "Item 2") ); } } @Readiness public class ReadinessCheck implements HealthCheck { @Override public HealthCheckResponse call() { return HealthCheckResponse.up("service-ready"); } } 🎯 Choose Your Path New to Java? Start with the Getting Started guide Want to try it? Visit our Try page for interactive examples Ready to install? Check our Download page Looking for inspiration? Browse the AppStore JBang adapts to your needs - whether you're learning, prototyping, or building production applications. ### [Learn](/learn/) ### [Learn](/posts/page2/) ### [Learn](/posts/page3/)