Representative interview topic

Coding interview: When should you use Java 25 compact source files?

CodingMedium
Offer.cc Editorial TeamPublished Updated

Question

After upgrading to Java 25, a team wants compact source files for small utilities. Explain entry-point selection, members of the implicitly declared class, automatic-import boundaries, and how you would decide whether to adopt the feature in production.

Prompt and scope

After upgrading to Java 25, a team wants compact source files for small utilities. Explain entry-point selection, members of the implicitly declared class, automatic-import boundaries, and how you would decide whether to adopt the feature in production.

JEP 512 finalizes Compact Source Files and Instance Main Methods after preview releases. A file can omit a top-level class declaration; top-level fields, methods, and nested declarations become members of an implicitly declared class, while the launcher still follows a defined protocol to find a launchable main method.

What the interviewer is testing

Cover the real class model behind the language sugar, the distinction between no-argument and array-argument entry points, java.base on-demand imports and the IO boundary, compile and run commands, documentation and debugging limits, and a gradual adoption strategy.

A 30-second answer

“I would position this as a boilerplate reduction for single-file scripts, teaching examples, and one-off tools. The compiler implicitly declares a class extending Object; top-level members become its members. The launcher first chooses an accessible void main(String[]) and otherwise an accessible void main(); an instance entry point is invoked after the default constructor runs. Java 25 imports public top-level types exported by java.base on demand, but IO static methods are not implicitly imported. I would keep explicit classes for public libraries and complex services, validating the target JDK, build tools, and documentation pipeline first.”

Step-by-step solution

Step 1: Identify the implicitly declared class

A source file without an enclosing class declaration implicitly declares a class. It extends Object, has a default no-argument constructor, and receives a host-chosen name that other source code cannot directly reference. Fields, methods, and nested declarations in the file become members of that class.

Step 2: Determine the launchable entry point

The launcher first searches for an accessible void main with a String-array parameter. If none exists, it searches for an accessible void main with no parameters. When the selected method is an instance method, the launcher constructs an instance of the implicit class before invoking it.

java
void main(String[] args) {
    IO.println("hello " + args[0]);
}

Step 3: Understand automatic imports

A compact source file acts as if it imports public top-level types exported by java.base on demand, so types such as Map and Stream usually need no explicit import. This does not import arbitrary modules, and IO static methods are not implicitly imported; call IO.println or add an explicit static import.

Step 4: Distinguish run and compile modes

You can run java Hello.java, allowing the launcher to compile and execute the file, or run javac Hello.java followed by the generated class. Direct source execution is convenient for short scripts; repeatable builds, caching, and release pipelines should still compile explicitly into a known output directory.

Step 5: Analyze state and lifecycle

Top-level fields become fields of the implicit class, so an instance main can read instance state. The launcher uses the default constructor, so external constructor-argument injection is not part of this protocol. Complex initialization, multiple entry points, or an explicit lifecycle are easier to review in a named class.

Step 6: Evaluate tooling boundaries

An implicitly declared class has no stable source-level name for other classes to reference, and javadoc cannot generate a conventional API for it. IDEs, static analyzers, coverage tools, and debuggers may support the syntax at different versions; validate the target JDK and build plugins together.

Step 7: Design migration and rollback

Move scripts or teaching examples into a separate directory first and keep an explicit-class version as a rollback. Pin Java 25 in CI, check that java and javac versions match, and verify packaging, logs, exception traces, and container commands do not depend on the implementation class name.

Step 8: Define the adoption boundary

Adoption depends on whether the code needs a stable API, dependency injection, observable entry points, or cross-module reuse. A single-file operations tool can become clearer with less ceremony; a shared library, long-running service, or documented API usually benefits from an explicit class.

Trade-offs and boundaries

Simplicity or discoverability

Compact files keep beginner examples close to their intent, but implicit names and import rules increase the amount of tooling knowledge required. Limit the feature to low-coupling directories and document that boundary in the coding standard.

Instance main or static main

An instance main can directly use fields and instance methods, which suits stateful demonstrations. A static main follows the traditional Java launch path more closely. Either way, keep the entry signature unambiguous so the launcher does not report multiple candidates.

Automatic imports or explicit imports

The java.base on-demand rule removes imports from teaching code, but types from other modules still need an explicit module or ordinary import. Shared production code should state dependencies clearly rather than relying on readers to remember implicit rules.

Failure drills and evolution

Multiple main candidates

Add both no-argument and String-array entry points and verify that the array form wins. Change a return type or visibility and verify the launcher’s error and fallback behavior.

Depending on an implicit class name

Have a second source file reference the generated class name, confirm compilation fails, and move shared logic into an explicitly named class.

Inconsistent toolchains

Compile the same file with an older JDK and Java 25. CI should fail immediately on the language-level mismatch instead of deferring a syntax failure to runtime.

Common mistakes and follow-ups

Mistake 1: Assuming the file has no class

Follow-up: Where do top-level methods and fields live? They are members of the compiler-generated implicit class and still follow member and instance-lifecycle rules.

Mistake 2: Assuming every import is automatic

Follow-up: Why must IO.println name the class? IO static methods are not implicitly imported; java.base public top-level types are imported on demand.

Mistake 3: Treating script syntax as a stable API

Follow-up: Can another module depend on the implicit class? It cannot rely on the implementation name; reusable APIs belong in explicitly named classes.

Deeper follow-ups and model answers

Why does an instance main need a default constructor?

After choosing an instance entry point, the launcher constructs the implicitly declared class and then invokes the method. Compact files have no constructor-argument protocol, so external injection requires an explicit class.

How does direct source execution differ from compilation first?

Direct execution lets the java launcher compile temporarily and run the source, which suits short scripts. Compiling with javac first gives CI and release systems stable artifacts, caching, and diagnostics.

When should you return to an explicit class?

Use an explicit named class when you need a stable type name, public API, javadoc, dependency injection, multiple construction paths, or cross-module reuse, even if the single-file form has more boilerplate.

Public sources

Related questions

Related interview tool

Use Screenshot for a coding prompt

Capture the problem, then work through the constraints, solution, code, edge cases, and complexity in order.

View the tool