DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Newsletter
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • Mastering Thread-Local Variables in Java: Explanation and Issues
  • Techniques You Should Know as a Kafka Streams Developer
  • Session State Design Patterns in Java: Client, Server, and Database Session State Design Patterns
  • Redis-Based Tomcat Session Management

Trending

  • Agentic Systems and Design Patterns
  • Event-Driven AI Systems With Kafka and Autonomous Agents
  • SpaceXAI Launches Grok 4.7: Low Prices, Heavy Token Use
  • Context Engineering: The Missing Piece in Agentic Systems
  1. DZone
  2. Coding
  3. Java
  4. Jakarta Faces Flow Scope: Managing Multi-Step UX Without Session State

Jakarta Faces Flow Scope: Managing Multi-Step UX Without Session State

Jakarta Faces @FlowScoped preserves state across views without using the entire session, making it ideal for wizards, onboarding, checkout, and multi-step workflows.

By 
Otavio Santana user avatar
Otavio Santana
DZone Core CORE ·
Sep. 29, 26 · Analysis
Likes (0)
Comment
Save
Tweet
Share
11 Views

Join the DZone community and get the full member experience.

Join For Free

Multi-step flows are common in UX, including onboarding, checkout, account setup, approval processes, configuration wizards, and administrative tasks. These require users to move through multiple screens while continuing a consistent working state. The challenge is to keep this state active for the duration of the interaction, but not beyond. Request scope is too short, while session scope often extends longer than the business process needs.

Jakarta Faces handles this with @FlowScoped, which manages state based on the lifecycle of a flow instead of a single page or the entire session. This article uses a customer segmentation application to demonstrate how a flow can guide users through configuration, preview, and confirmation, while maintaining state across each step. This approach creates a cleaner model for wizard-style UX: the scope begins when the user enters the flow, persists during navigation, and ends upon exit.

Why Jakarta Faces Still Matters

Jakarta Faces continues to be relevant because many enterprise applications are developed and maintained by teams with strong Java expertise. In these environments, a server-side UI framework limits context switching, keeps validation and navigation close to the application model, and allows teams to reuse the same language, dependency injection model, and enterprise APIs throughout the stack. Architecturally, if the team is proficient in Java and the application is form-driven, workflow-oriented, or back-office focused, introducing a separate frontend stack does not necessarily offer an advantage.

Component libraries such as PrimeFaces further support this approach. Rather than building tables, dialogs, forms, charts, wizards, and validation from scratch, teams can use reusable components within the Jakarta EE programming model. This can accelerate delivery and lessen the need for custom frontend infrastructure. While the decision should be based on product and team context, for Java-focused enterprise teams, Jakarta Faces is a pragmatic architectural choice, not just a legacy option.

A few publicly documented examples of organizations that have used Jakarta EE/JSF or PrimeFaces include:

  • NASA
  • Walmart Labs
  • Lufthansa
  • Rakuten
  • Commerzbank
  • United Nations
  • Penn State University
  • Harvard University
  • Telefonica
  • Big Lots
  • Comfortel (telecommunications)
  • Various commercial banks and financial institutions

Overview of Jakarta Faces Scopes

Jakarta Faces applications maintain managed bean state for varying durations. Choosing the right scope is an architectural decision and must match the user interaction's lifetime. Some state is limited to a single HTTP request, a single page, a multi-step flow, or the entire user session.

  • @RequestScoped is the shortest-lived scope. It creates a bean instance for each HTTP request and discards it when the request completes. It suits stateless actions, simple submissions, and operations that don't need to continue across navigation or Ajax interactions.
  • @ViewScoped retains the bean while the user stays on the same Faces view. It is ideal for pages with forms, tables, filtering, pagination, dialogs, or Ajax interactions that update the same page multiple times. The state is discarded when the user navigates to a different view.
  • @FlowScoped is intended for business interactions spanning multiple views, such as checkout, onboarding, configuration wizards, approval workflows, or customer segmentation. The bean remains active throughout the flow and is destroyed when the flow ends. Its lifecycle falls between view scope and session scope.
  • @SessionScoped maintains state for the entire user session. It suits information that remains across multiple pages, such as user preferences or session-level context. Avoid using it for temporary workflow state, as this can unnecessarily extend the state’s lifetime.
  • @ApplicationScoped has the broadest lifetime, sharing a single bean instance across the entire application and all users. It suits shared services, caches, configuration, or application-wide state, but not per-user or per-flow data unless explicitly designed for sharing and thread safety.

Overview of Jakarta Faces scopes


Building a Multi-Step Experience With @FlowScoped

This article focuses on Jakarta Faces flow, which models user engagements spanning multiple pages but shorter than a full HTTP session. In the customer-segmentation example, the administrator configures thresholds, previews their impact, reviews the configuration, and starts the operation. These steps form a single business process and should share the same state.

Jakarta Faces represents this process with a flow definition and a flow-scoped managed bean. In this project, the flow resides in the customer-segmentation directory:

reStructuredText
 
src/main/webapp/
└── customer-segmentation/
    ├── customer-segmentation-flow.xml
    ├── configure.xhtml
    ├── preview.xhtml
    └── review.xhtml


Aligning the directory, flow identifier, and bean name clarifies their relationship. Here, the flow stays named customer-segmentation, defined in customer-segmentation/customer-segmentation-flow.xml, and the Java bean uses @FlowScoped("customer-segmentation"). The value passed to @FlowScoped ties the bean’s lifecycle to the corresponding Faces flow.

The XML file defines the flow’s structure and navigation boundaries, but does not store business state. In this example, configure is the starting point, followed by preview and review. Each view has an identifier and references its corresponding XHTML document:

XML
 


<flow-definition id="customer-segmentation">
    <start-node>configure</start-node>

    <view id="configure">
        <vdl-document>
            /customer-segmentation/configure.xhtml
        </vdl-document>
    </view>

    <view id="preview">
        <vdl-document>
            /customer-segmentation/preview.xhtml
        </vdl-document>
    </view>

    <view id="review">
        <vdl-document>
            /customer-segmentation/review.xhtml
        </vdl-document>
    </view>

    <flow-return id="home">
        <from-outcome>/index?faces-redirect=true</from-outcome>
    </flow-return>
</flow-definition>


The start-node specifies where the interaction begins. The <view> elements define the flow’s pages, and <flow-return> determines how the application exits. Returning the outcome home ends the flow, redirects the user to the dashboard, and discards the flow-scoped state. Navigation within the flow preserves the state, while exiting ends the conversation.

On the Java side, CustomerSegmentationFlow manages the conversation state as both a named Faces bean and a flow-scoped bean:

Java
 
@Named
@FlowScoped("customer-segmentation")
public class CustomerSegmentationFlow implements Serializable {

    @Inject
    private CustomerSegmentationFlowService flowService;

    private CustomerSegmentationFlowState state;

    @PostConstruct
    public void initialize() {
        state = flowService.initializeState();
    }

    // ...
}


@Named makes the bean accessible to Faces pages through Expression Language, while @FlowScoped("customer-segmentation") assigns its lifecycle to the specific flow. The state created during @PostConstruct remains across requests and page transitions during the flow, so CustomerSegmentationFlowState remains available throughout the interaction.

This distinction sets @FlowScoped apart from @ViewScoped. With view scope, moving from configure.xhtml to preview.xhtml starts a new conversation. With flow scope, both views remain part of the same business interaction. The scope follows the conversation, not individual pages.

Navigation methods on the bean then return outcomes that correspond to nodes defined by the flow:

Java
 
public String preview() {
    flowService.preview(state);
    return "preview";
}

public String review() {
    return "review";
}


Returning "preview" moves the user to the preview view in the flow definition, while "review" advances to the next view. Since both destinations are within customer-segmentation, the same flow-scoped bean and its state remain active.

The final action demonstrates the other side of the lifecycle:

Java
 
public String execute() {
    long executionId = flowService.start(state);

    // message handling omitted

    return "home";
}


Home is not a page within the wizard. It corresponds to the <flow-return id="home"> element in the XML definition. When this outcome occurs, Faces exits the flow, redirects to the dashboard, and discards the associated state.

@FlowScoped is ideal for processes such as checkout, onboarding, registration, approval, configuration, and administrative wizards. It provides a scope broader than a single page but narrower than a session. Instead of using @SessionScoped for temporary workflow data, the state exists only for the workflow's duration.

This article covers the Faces flow: how pages are connected, how state persists between them, and how entering and exiting the flow manages the Java bean’s lifecycle. The full application also includes MongoDB persistence, dashboard services, preview calculations, and Jakarta Batch processing. The complete source code is available at https://github.com/soujava/mongodb-jakarta-batch. Here, the emphasis stays on the user interaction represented by Configure → Preview → Review → Exit.

Conclusion

@FlowScoped provides Jakarta Faces with an effective way to model multi-step user experiences as a single business conversation. Rather than storing temporary workflow state in @SessionScoped or reconstructing it between views, the flow maintains state only while the user progresses through the defined steps and releases it when the flow concludes. For Java-focused enterprise teams, this approach simplifies wizards, onboarding, approvals, checkout flows, and configuration processes by keeping navigation, state, and lifecycle consistent with the user experience.

Flow (web browser) Java (programming language) Session (web analytics)

Opinions expressed by DZone contributors are their own.

Related

  • Mastering Thread-Local Variables in Java: Explanation and Issues
  • Techniques You Should Know as a Kafka Streams Developer
  • Session State Design Patterns in Java: Client, Server, and Database Session State Design Patterns
  • Redis-Based Tomcat Session Management

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook