Single-File Executable Java Scripts Installed on the PATH
Overview
Java CLI Script applies the BCE pattern to self-contained, single-file Java 25 scripts launched
like any shell command. The shebang #!/usr/bin/env -S java --source 25 runs the file
in source-file mode: no build tool, no compilation step, no .java extension. The
filename is a short, lowercase command name (camelCase when needed, never dashes) and doubles as
the application name in all output.
Scripts are installed by copying or symlinking the file into a PATH directory such as
/usr/local/bin. They rely exclusively on java.base and standard JDK
modules; when a task genuinely requires external libraries or multiple files, the
Java CLI App style takes over.
Business Components
The business component is the single file. Short scripts stay flat: top-level methods, records,
and enums ordered Boundary, then Control, then Entity, with main last. When a script
grows beyond roughly two screens or accumulates several records and many methods, its members are
grouped into three interfaces named after the BCE layers. The interfaces are namespaces for
developer experience (IDE outline, per-layer folding, layer-labeled call sites), never enforcement
ceremony: no constructors, no final, no explicit visibility modifiers. Past roughly
1000 lines even a grouped single file fights the medium; switch to the Java CLI App style.
Boundary
In grouped scripts, interface Boundary holds the coarse-grained facade named after
the script's responsibility, output adapters such as a Log enum, and the
NAME and VERSION constants as bare interface fields.
main stays top-level at the very bottom and contains exactly one statement:
the invocation of the boundary facade.
Shebang: #!/usr/bin/env -S java --source 25; never --enable-preview, all features are standard
Entry Point: void main(String... args) throws Exception; one statement delegating to the facade
Dynamic Name: Derive the application name via MethodHandles.lookup().lookupClass(); nothing to hardcode
Version Tracking: String version = "YYYY-MM-DD.N"; printed as the first line of output
Flags: Support -help and -version only in scripts that accept arguments
Output: IO.println() to stdout, never System.out.println(); diagnostics via System.err
Piping: Read stdin when no file argument is given; exit 0 on success, non-zero on failure
Control
interface Control contains stateless static functions owning all I/O and traversal,
ordered coarsest first. Interface methods with bodies require the explicit static
modifier. Cross-layer references are qualified, so Entity.Layer in a signature labels
the layer at every call site.
Stateless Functions: Static methods; all file, network, and process I/O lives here
Coarsest First: The highest-level function opens the interface, helpers follow
Explicit static: Interface methods with bodies must declare static
Qualified References: Entity.X and Boundary.Log name the layer where they are used
JDK Only: java.net.http.HttpClient for HTTP, java.nio.file for files, javax.crypto for crypto
Entity
interface Entity groups the records and enums that maintain state and expose behavior
on that state. Entities perform no I/O. Records keep their own explicit static final
fields; records are classes, not interfaces.
Records: Immutable domain data with behavior bound directly to the data
Enums: Business states and categories with behavior
No I/O: All input and output stays in the Control layer
Sanctioned Names: Boundary, Control, Entity are layer namespaces, the sole exception to the rule that no type name may end with Control
Principles
Single File
Everything lives in one file: logic, records, enums, and helper methods. Deployment is
copying one file to the PATH; maintenance is editing it in place, with no build.
Zero Dependencies
Only java.base and standard JDK modules. Never add classpath entries to the
shebang; the sole exception is a convenience script wrapping an existing application JAR.
Source-File Mode
Java 25 runs the script directly from source. No .java extension, no
--enable-preview, no compilation step; the file is the deliverable.
Layers over Ceremony
Interfaces beat grouping classes and enums as namespaces: no default constructor and no
values() polluting completion. Grouping extends single-file viability
considerably.
Code Style
No Classes: An unnamed class with top-level members; the only named interfaces are the Boundary, Control, and Entity layer namespaces
No Package: Scripts never declare a package
Text Blocks: Multi-line output as a single IO.println with a text block, never consecutive println calls
Argument Parsing: Parse manually for a few flags; use the zargs enum pattern for multi-option scripts
Nested Name Lookup: Inside a nested type, derive the script name with getEnclosingClass(); plain lookupClass() reports the nested type
Installation
Executable: chmod +x scriptname
System-Wide: sudo cp scriptname /usr/local/bin/
Development: sudo ln -s $(pwd)/scriptname /usr/local/bin/scriptname to edit in place
Consistent Naming: The filename is the application name, used in version strings, help text, and error messages