Skip to content

ComponentFlow fails to run with clone().reset() pattern in 4.0.0-RC1 (regression from M1) #1233

@fbus

Description

@fbus

GitHub Issue: ComponentFlow fails with missing StringTemplate files in Spring Shell 4.0.0-RC1 (regression from 4.0.0-M1)

Description

When upgrading from Spring Shell 4.0.0-M1 to 4.0.0-RC1, ComponentFlow interactive flows fail with FileNotFoundException for StringTemplate (.stg) files. The required template files for rendering interactive components are missing from the classpath, making ComponentFlow unusable.

Environment

  • Spring Shell Version: 4.0.0-RC1
  • Previous Working Version: 4.0.0-M1
  • Spring Boot Version: 4.0.1
  • Java Version: 21
  • OS: Windows/Linux/macOS (all affected)

Expected Behavior

When a command method calls ComponentFlow.run(), the interactive TUI components (single item selector, string input, etc.) should be displayed to the user, allowing them to interactively provide input. The flow should complete successfully and return the user's selections in the ComponentContext.

This behavior worked correctly in Spring Shell 4.0.0-M1.

Actual Behavior

The interactive flow fails with a FileNotFoundException:

shell:>test
Parameters received - name: '', type: ''
Parameters missing, falling back to interactive mode...
Error running interactive flow: java.io.FileNotFoundException: class path resource [org/springframework/shell/component/single-item-selector-default.stg] cannot be opened because it does not exist
java.io.UncheckedIOException: java.io.FileNotFoundException: class path resource [org/springframework/shell/component/single-item-selector-default.stg] cannot be opened because it does not exist
        at org.springframework.shell.jline.tui.component.support.AbstractComponent.resourceAsString(AbstractComponent.java:346)
        at org.springframework.shell.jline.tui.component.support.AbstractComponent.renderTemplateResource(AbstractComponent.java:227)
        at org.springframework.shell.jline.tui.component.SingleItemSelector.access$000(SingleItemSelector.java:46)
        at org.springframework.shell.jline.tui.component.SingleItemSelector$DefaultRenderer.apply(SingleItemSelector.java:184)
        ...

The StringTemplate files (.stg) required by ComponentFlow components are missing from the classpath, preventing any interactive component from rendering.

Steps to Reproduce

I've created a minimal reproducible example:

1. Create a Spring Boot application with Spring Shell dependency

pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>4.0.1</version>
        <relativePath/>
    </parent>

    <groupId>com.example</groupId>
    <artifactId>spring-shell-componentflow-bug</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <java.version>21</java.version>
        <spring-shell.version>4.0.0-RC1</spring-shell.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.shell</groupId>
            <artifactId>spring-shell-starter</artifactId>
            <version>${spring-shell.version}</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <compilerArgs>
                        <arg>-parameters</arg>
                    </compilerArgs>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

2. Create a command that uses ComponentFlow

TestCommand.java:

package com.example.shellbug;

import java.util.HashMap;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.shell.core.command.annotation.Command;
import org.springframework.shell.core.command.annotation.Option;
import org.springframework.shell.jline.tui.component.context.ComponentContext;
import org.springframework.shell.jline.tui.component.flow.ComponentFlow;
import org.springframework.stereotype.Component;

@Component
public class TestCommand {

    @Autowired
    private ComponentFlow.Builder componentFlowBuilder;

    @Command(name = "test", description = "Test command with interactive flow")
    public void testCommand(
            @Option(description = "Name parameter", defaultValue = "") String name,
            @Option(description = "Type parameter", defaultValue = "") String type
    ) {
        System.out.println("Parameters received - name: '" + name + "', type: '" + type + "'");
        
        // If parameters are missing, use interactive flow
        if (name.isEmpty() || type.isEmpty()) {
            System.out.println("Parameters missing, falling back to interactive mode...");
            
            try {
                ComponentContext<?> result = runInteractiveFlow();
                
                // Extract values from interactive flow
                name = result.get("name", String.class);
                type = result.get("type", String.class);
                
                System.out.println("Interactive values - name: '" + name + "', type: '" + type + "'");
            } catch (Exception e) {
                System.err.println("Error running interactive flow: " + e.getMessage());
                e.printStackTrace();
                return;
            }
        }
        
        System.out.println("Final result - name: " + name + ", type: " + type);
    }

    public ComponentContext<?> runInteractiveFlow() {
        Map<String, String> typeItems = new HashMap<>();
        typeItems.put("rest", "REST Client");
        typeItems.put("soap", "SOAP Client");
        
        ComponentFlow flow = componentFlowBuilder.clone().reset()
                .withSingleItemSelector("type")
                    .name("Choose client type:")
                    .selectItems(typeItems)
                    .defaultSelect("rest")
                    .and()
                .withStringInput("name")
                    .name("Enter artifact name:")
                    .defaultValue("my-client")
                    .and()
                .build();

        return flow.run().getContext();
    }
}

3. Run the application

mvn clean package
java -jar target/spring-shell-componentflow-bug-1.0-SNAPSHOT.jar

4. Execute the command

In the shell prompt:

shell:>test

5. Observe the error

Expected output:

shell:>test
Parameters received - name: '', type: ''
Parameters missing, falling back to interactive mode...
[Interactive menu appears with client type selector]
[User makes selections]
Interactive values - name: 'my-client', type: 'rest'
Final result - name: my-client, type: rest

Actual output (4.0.0-RC1):

shell:>test
Parameters received - name: '', type: ''
Parameters missing, falling back to interactive mode...
Error running interactive flow: java.io.FileNotFoundException: class path resource [org/springframework/shell/component/single-item-selector-default.stg] cannot be opened because it does not exist
java.io.UncheckedIOException: java.io.FileNotFoundException: class path resource [org/springframework/shell/component/single-item-selector-default.stg] cannot be opened because it does not exist
        at org.springframework.shell.jline.tui.component.support.AbstractComponent.resourceAsString(AbstractComponent.java:346)
        at org.springframework.shell.jline.tui.component.support.AbstractComponent.renderTemplateResource(AbstractComponent.java:227)
        at org.springframework.shell.jline.tui.component.SingleItemSelector.access$000(SingleItemSelector.java:46)
        at org.springframework.shell.jline.tui.component.SingleItemSelector$DefaultRenderer.apply(SingleItemSelector.java:184)
        ...

Analysis

The ComponentFlow API has not been marked as deprecated, and there were no migration notes indicating changes to this functionality. The same code pattern works in 4.0.0-M1 but fails in 4.0.0-RC1, indicating a regression rather than an intentional API change.

Root cause: The StringTemplate Group files (.stg) required by ComponentFlow components are missing from the classpath in RC1. These template files are essential for rendering interactive components like SingleItemSelector, StringInput, etc.

Missing template files include (but are not limited to):

  • org/springframework/shell/component/single-item-selector-default.stg
  • Other component template files

This appears to be a packaging/build issue in the spring-shell-jline module where the .stg template resources are not being included in the JAR, or a classpath resolution issue when running as a Spring Boot executable JAR.

The minimal reproducible example includes two test commands:

  • test - Uses the clone().reset() pattern (fails with missing template error)
  • test-no-clone - Direct builder usage without clone/reset (likely same error)

Possible technical causes:

  1. Maven resource filtering configuration changed between M1 and RC1
  2. Template files moved to a different module not included in spring-shell-starter
  3. ClassLoader issues with Spring Boot's nested JAR structure in RC1
  4. Build configuration excluding .stg files from the final JAR

Impact

This is a blocking issue for GA release as it breaks existing applications that rely on interactive flows for user input. Many CLI tools use ComponentFlow to provide guided user experiences, and this regression makes them unusable.

Workaround

Currently, the only workaround is to stay on Spring Shell 4.0.0-M1, which is not ideal as it's a milestone release.

Additional Context

We discovered this issue when upgrading a production tool (honey-shell) that provides interactive Maven archetype generation. The tool has multiple commands that all rely on ComponentFlow for interactive mode when parameters are not provided via command line.

Related code patterns in our production codebase:

  • Multiple commands with optional parameters
  • Fallback to interactive ComponentFlow when parameters missing
  • Validation before calling ComponentFlow

All of these patterns work in 4.0.0-M1 but fail in 4.0.0-RC1.


Request

Given that 4.0.0 GA is approaching, could this be prioritized? I'm happy to provide additional debugging information or test patches if needed.

spring-shell-componentflow-bug.zip

Metadata

Metadata

Assignees

No one assigned

    Labels

    status/need-triageTeam needs to triage and take a first look

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions