Skip to content

fix: handle missing template files gracefully#2437

Open
maxandersen wants to merge 1 commit intojbangdev:mainfrom
maxandersen:2396-template-handling
Open

fix: handle missing template files gracefully#2437
maxandersen wants to merge 1 commit intojbangdev:mainfrom
maxandersen:2396-template-handling

Conversation

@maxandersen
Copy link
Copy Markdown
Collaborator

Fixes #2396

Problem

When a template references a file that doesn't exist, jbang crashes with NullPointerException:

[jbang] [ERROR] Cannot invoke "dev.jbang.resources.ResourceRef.getOriginalResource()"
because the return value of "dev.jbang.source.RefTarget.getSource()" is null

Root Cause

In RefTarget.create(), when ResourceResolver.resolve(ref) returns null (which happens when a file doesn't exist), the null value was passed directly to the RefTarget constructor, which expects a non-null ResourceRef.

Solution

Follows the established pattern from other parts of the codebase (DocRef, ProjectBuilder, Source) by creating an UnresolvableResourceRef when resolve() returns null.

This provides a clear error message instead of NPE:

Failed to get contents from resource 'templates/jbang/HelloToolkitApp.java': 
not resolvable from [resolver description]

Changes

  • Modified RefTarget.create() to check for null and use ResourceRef.forUnresolvable()
  • Added test case testInitWithMissingTemplateFile() to verify graceful failure

Testing

  • Added unit test that creates a template with missing file reference
  • Verifies command fails with non-zero exit code (not NPE)
  • Verifies no output files are created

🤖 Generated with Claude Code

Fixes jbangdev#2396 where jbang would crash with NullPointerException when
a template referenced a non-existent file.

The issue was in RefTarget.create() which passed null directly to
the constructor when ResourceResolver.resolve() couldn't find a file.

This fix follows the established pattern from DocRef, ProjectBuilder,
and Source by creating an UnresolvableResourceRef when resolve()
returns null, which provides a clear error message instead of NPE.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a crash when a template references a missing file by ensuring RefTarget.create() never constructs a RefTarget with a null ResourceRef, instead creating an unresolvable ResourceRef that yields a clear error.

Changes:

  • Update RefTarget.create(String, Path, ResourceResolver) to convert a null resolve result into ResourceRef.forUnresolvable(...).
  • Add a CLI test intended to cover jbang init behavior when a template file reference is missing.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
src/main/java/dev/jbang/source/RefTarget.java Prevents null ResourceRef from propagating, avoiding NPEs and enabling a clearer error.
src/test/java/dev/jbang/cli/TestInit.java Adds a regression test for init with a missing template file reference.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +359 to +375
Path cwd = Util.getCwd();
Path out = cwd.resolve("result.java");

// Create template with reference to non-existent file
int addResult = JBang.getCommandLine()
.execute("template", "add", "-f", cwd.toString(), "--name=bad-template",
"{basename}/{basename}App.java=tpl/NonExistentFile.java");
assertThat(addResult, is(0));

// Attempting to init with this template should fail gracefully with clear error
int result = JBang.getCommandLine()
.execute("init", "--template=bad-template", "test");

// Should fail with non-zero exit code
assertThat(result, not(0));
// Output file should not be created
assertThat(out.toFile().exists(), is(false));
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test currently asserts result.java is not created, but jbang init --template=bad-template test won’t write to result.java anyway (it will generate files based on the template’s target pattern, likely under cwd/test/...). This means the “no output files are created” assertion is not actually validating the behavior. Consider asserting that the expected generated directory/file for the template target (eg cwd.resolve("test") or the specific {basename}/{basename}App.java output) does not exist after the failing init.

Copilot uses AI. Check for mistakes.
Comment on lines +369 to +373
int result = JBang.getCommandLine()
.execute("init", "--template=bad-template", "test");

// Should fail with non-zero exit code
assertThat(result, not(0));
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test comment says the init should fail “with clear error”, but the test only checks for a non-zero exit code. To ensure this change prevents the prior NPE regression, capture stderr/stdout and assert that it contains the new unresolvable-resource message (and/or does not contain the previous NullPointerException text). BaseTest already provides helpers like captureOutput(...)/checkedRun(...) that can be used here.

Suggested change
int result = JBang.getCommandLine()
.execute("init", "--template=bad-template", "test");
// Should fail with non-zero exit code
assertThat(result, not(0));
int[] result = new int[1];
String output = captureOutput(() -> result[0] = JBang.getCommandLine()
.execute("init", "--template=bad-template", "test"));
// Should fail with non-zero exit code
assertThat(result[0], not(0));
assertThat(output, containsString("unresolvable"));
assertThat(output, containsString("NonExistentFile.java"));
assertThat(output, not(containsString("NullPointerException")));

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

jbang template does not handle bad template file

2 participants