Backing Beans and CDI

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against Jakarta Faces 4.1 (API jakarta.faces-api 4.1.2, part of Jakarta EE 11), which delegates its whole bean facility to Jakarta CDI 4.1, with Eclipse Mojarra 4.1.17 and Apache MyFaces Core 4.1.4 on Java 17 or later. Faces 4.0 (Jakarta EE 10, CDI 4.0) works the same way for everything below; the differences between releases are in What Changed per Version. The examples were compiled and run in a scratch Bookshelf WAR on Tomcat 11.0.26 with Weld 6.0.4.Final, on Mojarra and on MyFaces (checked on 2026-10-08). The two code samples that need an application server (an EJB and a JTA transaction) were compiled against the Jakarta APIs but not deployed; they are marked where they appear.

A backing bean is the Java object behind a view. Its properties receive the values the user typed, its methods are called when the user clicks, and its getters supply what the page shows. Faces 2.0 shipped its own bean facility for this (@ManagedBean and a managed-bean element in faces-config.xml). It was replaced by CDI in two steps, deprecation in the 2.x line and removal in 4.0, so a Faces 4.1 backing bean is an ordinary CDI bean that carries a name and a scope. This page covers what that means in practice: how a class becomes a bean, how beans are layered, how to reach the Faces objects from a bean, and how to leave @ManagedBean behind. The scopes themselves (request, view, session, application, conversation, window and flow) have a page of their own; here a scope is just an annotation that says how long an instance lives.

What a Backing Bean Is

A page like catalogue.xhtml binds components to a bean with two kinds of expression, both written in the Expression Language (see the expression language page):

  • a value expression such as #{catalogueBean.newTitle} reads and writes a property through the bean’s getter and setter;

  • a method expression such as #{catalogueBean.borrow(b.id)} calls an action, a listener, a validator or a converter method.

The tutorial lists what a bean property can be bound to (a component value, a component instance, a converter, a listener or a validator) and what its methods usually do (validate data, handle an event, decide navigation). A backing bean needs a no-argument constructor, properties with accessors, and the methods the view refers to.

Where a Bean Is Used in the Lifecycle

A bean does not run on its own. Faces calls it at fixed points of the request processing lifecycle. A small request-scoped bean that logs every call (the listing is in Appendix: The Lifecycle Probe) shows the order on a postback of a form with one input and one button:

Phase Call observed Why

Process Validations (phase 3)

constructor, then @PostConstruct

The first expression that mentions the bean creates the instance. On a postback nothing has touched the bean yet when validation starts.

Update Model Values (phase 4)

setName(...)

The converted and validated value is written to the property.

Invoke Application (phase 5)

save()

The action method named by the button runs.

Render Response (phase 6)

getName()

The page is rendered; the getter supplies the value shown.

end of the request

@PreDestroy

A request-scoped instance is destroyed with the request.

On the initial GET there is no postback: the instance is created in Render Response, the getter is called once, and the bean is destroyed with the request. The same run on MyFaces 4.1.4 produced the same sequence. Two consequences follow. A getter runs in every render and often several times (a table evaluates its value more than once per request, as the data tables page shows), so it must be cheap and free of side effects: no query, no counter. And a setter or an action can only rely on what is in the bean when that phase runs, which for a request-scoped bean is whatever the postback itself rebuilt. Work that must survive from the render to the postback needs a longer scope.

CDI @Named Beans

A class becomes a backing bean when it is a CDI bean that the Expression Language can reach by name. Two annotations do the job: jakarta.inject.Named gives the EL name, and a scope annotation says how long the instance lives and also makes the class a bean in an archive that discovers only annotated classes (Bean Discovery and beans.xml). The first example is the Bookshelf catalogue bean. It reads the books from a service when the view is created, and offers two actions that take the book id as a method parameter.

CatalogueBean.java — a view-scoped backing bean
package org.example.bookshelf.web;

import jakarta.annotation.PostConstruct;
import jakarta.faces.application.FacesMessage;
import jakarta.faces.context.FacesContext;
import jakarta.faces.view.ViewScoped;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import java.io.Serializable;
import java.util.List;
import org.example.bookshelf.model.Book;
import org.example.bookshelf.service.BookService;

@Named
@ViewScoped
public class CatalogueBean implements Serializable {

    private static final long serialVersionUID = 1L;

    @Inject
    private BookService service;

    @Inject
    private FacesContext facesContext;

    private List<Book> books;
    private String newTitle;

    @PostConstruct
    void load() {
        books = service.catalogue();
    }

    public List<Book> getBooks() {
        return books;
    }

    public String getNewTitle() {
        return newTitle;
    }

    public void setNewTitle(String newTitle) {
        this.newTitle = newTitle;
    }

    public void rename(long id) {
        service.rename(id, newTitle);
        books = service.catalogue();
        facesContext.addMessage(null, new FacesMessage("Renamed book " + id));
    }

    public void borrow(long id) {
        service.borrow(id, "reader");
        facesContext.addMessage(null, new FacesMessage("Borrowed book " + id));
    }
}

The view that uses it is plain Facelets. The table, the two buttons per row and the message list all talk to the bean through EL:

catalogue.xhtml — the view of the bean above
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="jakarta.faces.html">
<h:head><title>Catalogue</title></h:head>
<h:body>
  <h:form id="f">
    <h:messages id="msgs"/>
    <h:inputText id="newTitle" value="#{catalogueBean.newTitle}"/>
    <h:dataTable id="books" value="#{catalogueBean.books}" var="b">
      <h:column>#{b.id}</h:column>
      <h:column>#{b.title}</h:column>
      <h:column>
        <h:commandButton id="rn" value="rename" action="#{catalogueBean.rename(b.id)}"/>
        <h:commandButton id="bw" value="borrow" action="#{catalogueBean.borrow(b.id)}"/>
      </h:column>
    </h:dataTable>
  </h:form>
</h:body>
</html>

Running the page on both implementations, pressing rename on the second row with Renamed in the input wrote the new title through the service, reloaded the list and queued the message Renamed book 2; pressing borrow on the third row queued Borrowed book 3. Four points about the class deserve attention:

  • @ViewScoped here is jakarta.faces.view.ViewScoped, not a scope from jakarta.enterprise.context. The bean survives postbacks of the same view, which is why the list loaded in @PostConstruct is still there when a button is pressed. Because the scope is passivating, the bean implements Serializable and so must everything it holds that is not a proxy (What a Backing Bean Must Satisfy).

  • The bean does not own the data. It asks BookService for the list and for the changes. Layering: View Bean, Service, Repository explains why.

  • @Inject FacesContext gives the bean the context of the current request without a static call. Injecting Faces Objects lists what else can be injected.

  • The action methods take the id as a parameter. EL method parameters are Jakarta Expression Language 3.0 and later; the expression language page covers the syntax.

The Default Name and an Explicit Name

@Named without a value gives the bean the unqualified class name with the first character converted to lower case. Faces users meet that rule constantly: CatalogueBean is #{catalogueBean}, not #{catalogue}. The rule is only the first character, so a class called URLBean is uRLBean. To choose a different name, pass it as the value:

CustomNamed.java — an explicit EL name
package org.example.bookshelf.web;

import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Named;

@Named("shelf")
@RequestScoped
public class CustomNamed {

    public String getHello() {
        return "custom";
    }
}

With this class the expression #{shelf.hello} printed custom, and #{customNamed.hello} printed an empty string: once a name is given, the default name no longer exists. Note how the failure looks. In an output position an unresolvable name is not an error. The page renders, with nothing where the value should be (it was the same on Mojarra and MyFaces). In an action or a listener the same mistake is an error: Target Unreachable, identifier [contextBean] resolved to null (Mojarra), reported when the button is pressed. A blank where a value should be is therefore the first sign of a bean that was not discovered, which is the next topic.

Bean Discovery and beans.xml

CDI only offers the beans it discovered. A bean archive has a bean discovery mode, set by WEB-INF/beans.xml. The project setup page explains the three modes and the file. For backing beans the practical result is this table, from runs on Tomcat 11.0.26 with Weld 6.0.4.Final (Mojarra and MyFaces gave the same results):

beans.xml @Named @RequestScoped bean @Named bean with no scope annotation

bean-discovery-mode="annotated"

found

not found: @Named is not a bean defining annotation, so the name resolves to nothing

bean-discovery-mode="all"

found

found, as a @Dependent bean

empty beans.xml file

found

not found: since CDI 4.0 an empty file means annotated

no beans.xml

not found: no bean of the WAR resolved, and a button referring to one failed with Target Unreachable

not found

The consequences for a Faces project are these:

  • Put a scope on every backing bean, and let the build use annotated. A bean with only @Named works under all and silently disappears under annotated, which is the default, so code that "worked on my machine" can fail in another archive. The bean defining annotations are the normal scopes (@RequestScoped, @ApplicationScoped, @SessionScoped, @ConversationScoped, jakarta.faces.view.ViewScoped, and the other normal scopes), @Dependent, interceptor and stereotype annotations.

  • all makes every class of the WAR a bean, including classes that were never meant to be, and slows start-up. It is rarely the right default for an application you control.

  • On Tomcat with Weld, ship WEB-INF/beans.xml. The CDI specification also lets an archive with bean defining annotations be an implicit bean archive without the file, and that works on a Jakarta EE server; in this run on Tomcat (Weld Servlet 6.0.4) no bean was found without the file. The project setup page makes the same point, and the Tomcat and Weld page covers the wiring of the container.

  • Faces itself needs CDI. On Tomcat without Weld, Mojarra 4.1 does not start (the project setup page shows the error).

What a Backing Bean Must Satisfy

CDI places a few rules on a bean class beyond the annotations:

  • Normal-scoped beans are proxied. @RequestScoped, @ViewScoped, @SessionScoped, @ApplicationScoped and the other normal scopes hand out a client proxy, not the instance, so the class must be proxyable: not final, with no final methods that the view calls, and with a non-private constructor with no parameters. Remove that constructor and the application does not start. Weld refuses to deploy with WELD-001435, whose message says that the normal scoped bean class is not proxyable because it has no no-args constructor, and names the class. This matters when moving to constructor injection (Constructor or Field Injection).

  • Passivating scopes need serialisable state. @SessionScoped, @ConversationScoped and jakarta.faces.view.ViewScoped are passivating. A bean in such a scope must be Serializable, and every dependency injected into it must be passivation capable too. Adding @Inject Helper helper to the view-scoped catalogue bean, where Helper was a @Dependent class that was not Serializable, stopped deployment with WELD-001413: the bean "declares a passivating scope but has a non-passivation-capable dependency". Injected normal-scoped beans (the service, FacesContext) are fine because the field holds a serialisable proxy.

  • Properties follow the JavaBeans convention. getX/isX and setX; EL resolves #{bean.x} through them, not through the field.

  • Lifecycle callbacks are @PostConstruct and @PreDestroy, from Jakarta Annotations. Use @PostConstruct for work that needs the injected fields: they are not set yet in the constructor.

A Stereotype for View Beans

If most backing beans are @Named @ViewScoped, a CDI stereotype names the combination once. A stereotype that carries @Named gives the bean a name by the same default-name rule, and one that carries a scope applies that scope. Stereotypes are bean defining annotations, so the bean is discovered under annotated.

ViewBean.java and ReviewDraftBean.java — a stereotype and a bean that uses it
package org.example.bookshelf.web;

import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import jakarta.enterprise.inject.Stereotype;
import jakarta.faces.view.ViewScoped;
import jakarta.inject.Named;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Named
@ViewScoped
@Stereotype
@Retention(RUNTIME)
@Target(TYPE)
public @interface ViewBean {
}

package org.example.bookshelf.web;

import java.io.Serializable;

@ViewBean
public class ReviewDraftBean implements Serializable {

    private static final long serialVersionUID = 1L;

    private String comment;

    public String getComment() {
        return comment;
    }

    public void setComment(String comment) {
        this.comment = comment;
    }
}

The page that held #{reviewDraftBean.comment} rendered an input on both implementations, so the name derived from the class and the scope from the stereotype.

Layering: View Bean, Service, Repository

The most important design decision about backing beans is what they should not do. A backing bean exists to serve one view: it holds what the user typed and what the page shows, and it translates a button press into a call. Business rules, queries and transactions belong lower down, where more than one view (and a test, a REST endpoint or a scheduled job) can use them. The usual layering for Bookshelf is:

catalogue.xhtml  ->  CatalogueBean (view bean, @ViewScoped)
                       ->  BookService (@ApplicationScoped, business rules)
                              ->  BookRepository (interface)
                                     ->  InMemoryBookRepository (@ApplicationScoped)

Each layer knows the one below, never the one above. The view bean injects the service; the service injects the repository interface; nothing but the view bean knows about FacesContext. That last rule is the one to keep: a service that takes a FacesContext, an ExternalContext or a FacesMessage cannot be called from a REST endpoint or tested without Faces. The view bean translates: it calls the service, catches the service’s exceptions, and turns them into messages.

The Layers in Code

The data side is a repository interface and one implementation:

BookRepository.java and InMemoryBookRepository.java — the data access boundary
package org.example.bookshelf.service;

import java.util.List;
import java.util.Optional;
import org.example.bookshelf.model.Book;

public interface BookRepository {

    List<Book> findAll();

    Optional<Book> findById(long id);

    Book save(Book book);
}

package org.example.bookshelf.service;

import jakarta.enterprise.context.ApplicationScoped;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentSkipListMap;
import org.example.bookshelf.model.Book;

@ApplicationScoped
public class InMemoryBookRepository implements BookRepository {

    private final Map<Long, Book> books = new ConcurrentSkipListMap<>();

    public InMemoryBookRepository() {
        save(new Book(1, "Pride and Prejudice", "Jane Austen", 1813));
        save(new Book(2, "Emma", "Jane Austen", 1815));
        save(new Book(3, "Frankenstein", "Mary Shelley", 1818));
        save(new Book(4, "Dracula", "Bram Stoker", 1897));
    }

    @Override
    public List<Book> findAll() {
        return new ArrayList<>(books.values());
    }

    @Override
    public Optional<Book> findById(long id) {
        return Optional.ofNullable(books.get(id));
    }

    @Override
    public Book save(Book book) {
        books.put(book.id(), book);
        return book;
    }
}

The service takes the repository through its constructor and fires a CDI event when a book is borrowed:

BookService.java — business rules with constructor injection
package org.example.bookshelf.service;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Event;
import jakarta.inject.Inject;
import java.util.List;
import java.util.Optional;
import org.example.bookshelf.model.Book;

@ApplicationScoped
public class BookService {

    private final BookRepository repository;
    private final Event<BookBorrowed> borrowed;

    @Inject
    public BookService(BookRepository repository, Event<BookBorrowed> borrowed) {
        this.repository = repository;
        this.borrowed = borrowed;
    }

    protected BookService() {
        this(null, null);
    }

    public List<Book> catalogue() {
        return repository.findAll();
    }

    public Optional<Book> find(long id) {
        return repository.findById(id);
    }

    @Audited
    public Book rename(long id, String title) {
        Book old = repository.findById(id).orElseThrow(() -> new IllegalArgumentException("No book " + id));
        return repository.save(new Book(old.id(), title, old.author(), old.year()));
    }

    @Audited
    public void borrow(long id, String borrower) {
        repository.findById(id).orElseThrow(() -> new IllegalArgumentException("No book " + id));
        borrowed.fire(new BookBorrowed(id, borrower));
    }
}

Constructor or Field Injection

BookService uses constructor injection, which makes the dependencies visible and lets a test build the class with new BookService(repository, null) without a container. The cost for a normal-scoped bean is the extra protected constructor with no parameters, which only exists so that the container can build the client proxy; it delegates with null arguments so that the final fields are assigned, and its instance is never used for real work. Delete it and deployment fails, as What a Backing Bean Must Satisfy showed. For a view bean the usual choice is field injection (@Inject private BookService service;), as in the catalogue bean: the bean is not built by hand in tests, the proxy constructor comes for free, and Faces objects are injected the same way. Both styles work in Mojarra and MyFaces; the choice is about testability, not about Faces.

Swapping an Implementation

Only one bean may satisfy @Inject BookRepository. Two implementations in the WAR make the injection ambiguous and the deployment fail. To replace the repository for a test or a demo without touching the service, make the second implementation an alternative and give it a priority, which enables it for the whole application:

SeededBookRepository.java — an alternative that wins when present
package org.example.bookshelf.service;

import jakarta.annotation.Priority;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Alternative;
import java.util.List;
import java.util.Optional;
import org.example.bookshelf.model.Book;

@Alternative
@Priority(100)
@ApplicationScoped
public class SeededBookRepository implements BookRepository {

    private final Book only = new Book(1, "Test Book", "Test Author", 2000);

    @Override
    public List<Book> findAll() {
        return List.of(only);
    }

    @Override
    public Optional<Book> findById(long id) {
        return id == 1 ? Optional.of(only) : Optional.empty();
    }

    @Override
    public Book save(Book book) {
        return book;
    }
}

With that class added to the WAR, detail.xhtml?id=1 showed Test Book and ?id=2 showed nothing (the alternative knows one book), with no change to the service or the view bean. A qualifier annotation is the other way to choose between implementations, when both must exist at the same time.

Decoupling with CDI Events

BookService.borrow does not know who cares that a book was borrowed. It fires a BookBorrowed event, and any bean with an @Observes method receives it (Faces' own system events are not CDI events; the bridge in the events page connects the two):

BorrowRegister.java — an observer, kept in the application scope
package org.example.bookshelf.service;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Named;
import jakarta.enterprise.event.Observes;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

@Named
@ApplicationScoped
public class BorrowRegister {

    private final List<BookBorrowed> loans = new CopyOnWriteArrayList<>();

    void onBorrowed(@Observes BookBorrowed event) {
        loans.add(event);
    }

    public List<BookBorrowed> loans() {
        return List.copyOf(loans);
    }
}

After pressing borrow on book 3, #{borrowRegister.loans()} printed [BookBorrowed[bookId=3, borrower=reader]]. This is the CDI event mechanism, not the Faces event model (action, value change, phase and system events), which has its own page.

Cross-Cutting Concerns with Interceptors

An interceptor binding adds behaviour to methods of a bean without editing them. The one below logs entry and exit of the service methods it is placed on, and stands in for what a transaction or a security check does:

Audited.java and AuditInterceptor.java — an interceptor binding and its interceptor
package org.example.bookshelf.service;

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import jakarta.interceptor.InterceptorBinding;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@InterceptorBinding
@Retention(RUNTIME)
@Target({TYPE, METHOD})
public @interface Audited {
}

package org.example.bookshelf.service;

import jakarta.annotation.Priority;
import jakarta.inject.Inject;
import jakarta.interceptor.AroundInvoke;
import jakarta.interceptor.Interceptor;
import jakarta.interceptor.InvocationContext;

@Audited
@Interceptor
@Priority(Interceptor.Priority.APPLICATION)
public class AuditInterceptor {

    @Inject
    AuditLog log;

    @AroundInvoke
    Object audit(InvocationContext ctx) throws Exception {
        log.add("before " + ctx.getMethod().getName());
        try {
            return ctx.proceed();
        } finally {
            log.add("after " + ctx.getMethod().getName());
        }
    }
}

package org.example.bookshelf.service;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Named;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

@Named
@ApplicationScoped
public class AuditLog {

    private final List<String> entries = new CopyOnWriteArrayList<>();

    public void add(String entry) {
        entries.add(entry);
    }

    public List<String> entries() {
        return List.copyOf(entries);
    }
}

@Audited is already on rename and borrow in BookService. After one rename and one borrow, the log read [before rename, after rename, before borrow, after borrow] on both implementations. The @Priority on the interceptor enables it for the whole application, so no beans.xml entry is needed. Interceptors apply to calls that go through the container: a call from one method of the same instance to another does not pass through the proxy, so the interceptor is skipped.

Testing the Layers Without Faces

The payoff of the layering is that the lower layers are plain Java. The service test needs neither Faces, nor CDI, nor a server:

BookServiceTest.java — a JUnit 5 test of the service
package org.example.bookshelf.service;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

import org.example.bookshelf.model.Book;
import org.junit.jupiter.api.Test;

class BookServiceTest {

    private final BookRepository repository = new InMemoryBookRepository();
    private final BookService service = new BookService(repository, null);

    @Test
    void renameKeepsAuthorAndYear() {
        Book renamed = service.rename(1, "Sense and Sensibility");

        assertEquals("Sense and Sensibility", renamed.title());
        assertEquals("Jane Austen", renamed.author());
        assertEquals(1813, renamed.year());
    }

    @Test
    void renamingAnUnknownBookFails() {
        assertThrows(IllegalArgumentException.class, () -> service.rename(99, "Nothing"));
    }
}

Both tests passed (JUnit Jupiter 5.13.4). Testing a view bean and the page that uses it needs the container and, for the page, a browser or an HTTP client; the testing page covers bean unit tests in more depth, as well as Arquillian, HtmlUnit tests and browser tests.

Injecting Faces Objects

Before CDI integration, a bean reached the Faces objects through the static FacesContext.getCurrentInstance() and the chain of getters that follow it. Since Faces 2.3 the specification requires that these objects be injectable with @Inject (section 5.6.1). The objects are FacesContext, ExternalContext, Flash, ResourceHandler and the servlet HttpSession. Faces 4.1 adds the current Flow. The maps are not beans of a type of their own: each map that ExternalContext returns is injected by combining @Inject with a qualifier annotation from jakarta.faces.annotation on a field of the matching Map type.

Qualifier (with @Inject) Field type Map injected

@ApplicationMap

Map<String, Object>

ExternalContext.getApplicationMap()

@SessionMap

Map<String, Object>

ExternalContext.getSessionMap()

@RequestMap

Map<String, Object>

ExternalContext.getRequestMap()

@ViewMap

Map<String, Object>

UIViewRoot.getViewMap()

@FlowMap

Map<Object, Object>

FlowHandler.getCurrentFlowScope()

@RequestParameterMap

Map<String, String>

ExternalContext.getRequestParameterMap()

@RequestParameterValuesMap

Map<String, String[]>

ExternalContext.getRequestParameterValuesMap()

@HeaderMap

Map<String, String>

ExternalContext.getRequestHeaderMap()

@HeaderValuesMap

Map<String, String[]>

ExternalContext.getRequestHeaderValuesMap()

@RequestCookieMap

Map<String, Object>

ExternalContext.getRequestCookieMap()

@InitParameterMap

Map<String, String>

ExternalContext.getInitParameterMap()

All eleven qualifiers are in the package jakarta.faces.annotation of the 4.1 API, together with @ManagedProperty (next section), @FacesConfig and @View. The following bean uses most of them and prints what it received. It is not a pattern to copy into an application; it is a way to see each injection point work.

ContextBean.java — every injectable Faces object in one bean
package org.example.bookshelf.web;

import jakarta.enterprise.context.RequestScoped;
import jakarta.faces.annotation.HeaderMap;
import jakarta.faces.annotation.InitParameterMap;
import jakarta.faces.annotation.ManagedProperty;
import jakarta.faces.annotation.RequestCookieMap;
import jakarta.faces.annotation.RequestMap;
import jakarta.faces.annotation.RequestParameterMap;
import jakarta.faces.annotation.SessionMap;
import jakarta.faces.annotation.ViewMap;
import jakarta.faces.application.FacesMessage;
import jakarta.faces.context.ExternalContext;
import jakarta.faces.context.FacesContext;
import jakarta.faces.context.Flash;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import jakarta.faces.application.ResourceHandler;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import java.util.Map;

@Named
@RequestScoped
public class ContextBean {

    @Inject
    FacesContext facesContext;

    @Inject
    ExternalContext externalContext;

    @Inject
    Flash flash;

    @Inject
    HttpSession session;

    @Inject
    HttpServletRequest servletRequest;

    @Inject
    ResourceHandler resourceHandler;

    @Inject
    @RequestParameterMap
    Map<String, String> params;

    @Inject
    @HeaderMap
    Map<String, String> headers;

    @Inject
    @InitParameterMap
    Map<String, String> initParams;

    @Inject
    @SessionMap
    Map<String, Object> sessionMap;

    @Inject
    @RequestMap
    Map<String, Object> requestMap;

    @Inject
    @ViewMap
    Map<String, Object> viewMap;

    @Inject
    @RequestCookieMap
    Map<String, Object> cookies;

    @Inject
    @ManagedProperty("#{param.id}")
    Long id;

    @Inject
    @ManagedProperty("#{param.id}")
    String idText;

    @Inject
    @ManagedProperty("#{request.contextPath}")
    String contextPath;

    @Inject
    @ManagedProperty("#{missing.property}")
    String missing;

    public String getSummary() {
        return "stage=" + initParams.get("jakarta.faces.PROJECT_STAGE")
            + " id=" + params.get("id")
            + " idLong=" + id + " idText=" + idText
            + " ctx=" + contextPath
            + " ua=" + headers.get("user-agent")
            + " path=" + externalContext.getRequestContextPath()
            + " viewId=" + facesContext.getViewRoot().getViewId()
            + " sessionIdSame=" + session.getId().equals(externalContext.getSessionId(false))
            + " missing=" + missing
            + " method=" + servletRequest.getMethod()
            + " rh=" + (resourceHandler != null)
            + " principal=" + externalContext.getUserPrincipal();
    }

    public void touchSession() {
        sessionMap.put("visits", (Integer) sessionMap.getOrDefault("visits", 0) + 1);
        requestMap.put("note", "hello");
        facesContext.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO, "Visits: " + sessionMap.get("visits"), null));
    }

    public String getCookieNames() {
        return String.join(",", cookies.keySet());
    }
}
context.xhtml — shows the values
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="jakarta.faces.html">
<h:head><title>Context</title></h:head>
<h:body>
  <h:form id="f">
    <p id="summary">#{contextBean.summary}</p>
    <p id="cookies">cookies=#{contextBean.cookieNames}</p>
    <h:commandButton id="touch" value="touch" action="#{contextBean.touchSession}"/>
    <h:messages id="msgs"/>
  </h:form>
</h:body>
</html>

Requesting context.xhtml?id=7 on both implementations printed:

stage=Development id=7 idLong=7 idText=7 ctx=/bookshelf path=/bookshelf viewId=/context.xhtml
sessionIdSame=true missing= method=GET rh=true principal=null

What the run showed about injection of Faces objects:

  • Each one is a contextual reference, usually a proxy. Injecting FacesContext into an application-scoped or view-scoped bean is therefore legitimate: the proxy resolves to the context of the request that is running when a method is called. A FacesContext copied out of getCurrentInstance() into a field of such a bean is the mistake, because the implementation releases and may reuse the instance when the request ends.

  • Outside a request there is no instance to resolve. An application-scoped bean that observed @Initialized(ApplicationScoped.class) and tried to use the injected FacesContext at start-up failed with a ContextNotActiveException on both implementations. The exception names the scope (jakarta.enterprise.context.RequestScoped on Mojarra, the internal org.apache.myfaces.cdi.FacesScoped on MyFaces). Start-up code, scheduled jobs and asynchronous tasks cannot touch the Faces objects; pass them the data instead.

  • @Inject HttpServletRequest also worked on Tomcat with Weld, although the specification lists only HttpSession. That is a feature of the servlet CDI integration, not of Faces, and it is not available where that integration is absent. @Inject Principal was not satisfied on Tomcat with Weld (deployment failed with WELD-001408: Unsatisfied dependencies for type Principal), so read the user from externalContext.getUserPrincipal() (it printed null for an anonymous request) or from the Jakarta Security SecurityContext where available.

  • The map qualifiers return live maps. The @SessionMap field of the example, filled in touchSession, produced the message Visits: 1. A session map change is a session attribute change; keep it to small, serialisable values (see the scopes page for what belongs in a session).

  • Property values can come from the scope of the page: @ViewMap is the map behind #{viewScope}.

Beyond injection, the specification also requires that the Facelets and programmatic implicit objects be resolved by CDI. That is why #{facesContext}, #{externalContext}, #{flash}, #{param}, #{header}, #{initParam}, #{cookie}, #{request}, #{session}, #{application}, #{view}, #{viewScope}, #{sessionScope}, #{requestScope}, #{applicationScope}, #{flow}, #{flowScope}, #{cc}, #{component} and #{resource} work in any page and in a @ManagedProperty expression. The expression language page lists each with its source; the practical rule is that a bean whose name equals one of them shadows or collides with it, so never call a bean request, session, param or flash.

Qualifier Literals for Programmatic Lookup

Faces 4.0 added an AnnotationLiteral to every one of these qualifiers (the 4.0 change log lists "New annotation literals for all @Qualifiers", issue 1582). Each qualifier has a nested class Literal with a constant INSTANCE (RequestMap.Literal.INSTANCE, SessionMap.Literal.INSTANCE, RequestParameterMap.Literal.INSTANCE and so on), and the two qualifiers that carry a value, @ManagedProperty and @View, have a factory of(String) as well. The literals exist for code that cannot use @Inject on a field: a utility class, a producer that picks a map at run time, a CDI extension that must name a qualifier, or a test. The bean below looks the maps up with CDI.current().select(...) and a TypeLiteral, because a Map<String, Object> is a parameterised type:

LiteralsBean.java — looking the maps up with qualifier literals
package org.example.bookshelf.web;

import jakarta.enterprise.context.RequestScoped;
import jakarta.enterprise.inject.spi.CDI;
import jakarta.enterprise.util.TypeLiteral;
import jakarta.faces.annotation.RequestMap;
import jakarta.faces.annotation.RequestParameterMap;
import jakarta.faces.annotation.SessionMap;
import jakarta.inject.Named;
import java.util.Map;

@Named
@RequestScoped
public class LiteralsBean {

    public String getReport() {
        CDI<Object> cdi = CDI.current();
        Map<String, String> params = cdi.select(new TypeLiteral<Map<String, String>>() { },
                RequestParameterMap.Literal.INSTANCE).get();
        Map<String, Object> session = cdi.select(new TypeLiteral<Map<String, Object>>() { },
                SessionMap.Literal.INSTANCE).get();
        Map<String, Object> request = cdi.select(new TypeLiteral<Map<String, Object>>() { },
                RequestMap.Literal.INSTANCE).get();

        session.put("visits", 1);
        request.put("seen", "yes");
        return "param.id=" + params.get("id") + " session.visits=" + session.get("visits")
                + " request.seen=" + request.get("seen");
    }
}

literals.xhtml?id=3, which prints #{literalsBean.report}, rendered param.id=3 session.visits=1 request.seen=yes on Mojarra 4.1.17 and on MyFaces 4.1.4. One limit showed in the same run: cdi.select(Long.class, ManagedProperty.Literal.of("#{param.id}")) did not behave like an injected @ManagedProperty field. On both implementations it found no bean when no bean in the application had a matching @ManagedProperty injection point; once one had, MyFaces returned the value and Mojarra threw a NullPointerException, presumably because there is no injection point to read the expression from in a programmatic lookup. Read parameters with the map literals shown above, or declare the @ManagedProperty field, and do not use the @ManagedProperty literal for lookup.

jakarta.faces.annotation.ManagedProperty

The old @ManagedProperty (a javax.faces.bean annotation on a field with a setter) is gone with the rest of the native facility. The CDI replacement has the same name and sits in jakarta.faces.annotation. It is a CDI qualifier, so it only has an effect together with @Inject, on a field; its @Target is FIELD, so it cannot be put on a setter or a constructor parameter. Its single element value is an EL expression. When the bean is created, the container evaluates the expression with Application.evaluateExpressionGet(FacesContext, String, Class) and injects the result, coerced to the field’s type. Because that call needs the current FacesContext, the bean must be created inside a Faces request.

The case it was designed for is making a request parameter, a resource-bundle entry or an implicit object available to a bean without reading maps by hand:

DetailBean.java — the book id from the query string
package org.example.bookshelf.web;

import jakarta.enterprise.context.RequestScoped;
import jakarta.faces.annotation.ManagedProperty;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import org.example.bookshelf.model.Book;
import org.example.bookshelf.service.BookService;

@Named
@RequestScoped
public class DetailBean {

    @Inject
    @ManagedProperty("#{param.id}")
    private Long id;

    @Inject
    private BookService service;

    public Book getBook() {
        return id == null ? null : service.find(id).orElse(null);
    }
}
detail.xhtml — the view that shows the book
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="jakarta.faces.html">
<h:head><title>Detail</title></h:head>
<h:body>
  <p id="book">#{detailBean.book.title} by #{detailBean.book.author}</p>
</h:body>
</html>

detail.xhtml?id=3 rendered Frankenstein by Mary Shelley on Mojarra and on MyFaces. The behaviour at the edges, from the same runs, decides when to use it:

Situation Result

id parameter absent, field of type Long

the field is null; getBook() returns null and the page shows an empty title

id absent, field of type String

an empty string (not null); the same for an expression that resolves to nothing, such as #{missing.property}

id=abc, field of type Long

the request fails while the bean is created: Cannot convert [abc] of type [class java.lang.String] to [class java.lang.Long] (an ELException) and an HTTP 500 error page, on both implementations

@ManagedProperty("#{param.id}") in an @ApplicationScoped bean, requests ?id=1 then ?id=2

the bean kept 1: the expression is evaluated once, when the bean is created

@ManagedProperty("#{request.contextPath}")

/bookshelf: any EL expression that Faces can evaluate works

So @ManagedProperty is a good fit for a request-scoped bean that wants a value the request carries. Two limits make it a poor general tool for parameters. The conversion is the plain EL coercion and there is no validation or message: a malformed number is an exception, not a field error. And the value is read at creation. For a bean that outlives the request, as the application-scoped run showed, it is frozen at whatever the first request carried; the same applies to any longer scope, since the rule is "evaluated when the container creates the instance". The Faces tool for request parameters that become part of the view is f:viewParam, with converters, validators, messages and f:viewAction; the view parameters and view actions page covers it. Use @ManagedProperty for "give me #{param.x} or #{request.contextPath}`" and `f:viewParam for "this page needs a book id that must be a valid number".

The alternative that reads a request parameter without any expression is the injected map, which returns the raw String or null:

@Inject
@RequestParameterMap
private Map<String, String> params;

String raw = params.get("id");

Producers: Exposing Computed Values

A producer method turns the result of code into an injectable bean. With @Named it also becomes a name for EL, which is useful for values that are not worth a class of their own:

Producers.java — the view’s locale as a named bean
package org.example.bookshelf.web;

import jakarta.enterprise.context.Dependent;
import jakarta.enterprise.inject.Produces;
import jakarta.faces.context.FacesContext;
import jakarta.inject.Named;
import java.util.Locale;

@Dependent
public class Producers {

    @Produces
    @Named("currentLocale")
    Locale currentLocale(FacesContext context) {
        return context.getViewRoot() != null ? context.getViewRoot().getLocale() : context.getExternalContext().getRequestLocale();
    }
}

#{currentLocale} printed en_US in misc.xhtml on both implementations. The producer method takes the FacesContext as a parameter, which CDI injects. It has the default scope, @Dependent: the value is computed for every reference. A producer for a normal scope (for example @RequestScoped) would need the produced type to be proxyable, and a final class such as Locale is not (the CDI rules for unproxyable bean types). A @Dependent producer that reads the FacesContext also depends on the call happening inside a request, like every other use of the Faces objects.

EJB and @Transactional Services

On a Jakarta EE application server the service layer is often an Enterprise Bean, or a CDI bean with a transaction declared by annotation. Both are injectable into a backing bean. The sample below was compiled against the Jakarta Enterprise Beans 4.0, Transactions 2.0 and Persistence 3.2 APIs, and was not deployed to a server: Tomcat has no Enterprise Beans container and no JTA, and the run on it logged WELD-000101: Transactional services not available. Injection of @Inject UserTransaction not available.

ReviewService.java and LoanService.java — an EJB and a transactional CDI bean
package org.example.bookshelf.service;

import jakarta.ejb.Stateless;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;

@Stateless
public class ReviewService {

    @PersistenceContext
    private EntityManager em;

    public void add(long bookId, String text) {
        em.createNativeQuery("insert into review(book_id, text) values (?1, ?2)")
            .setParameter(1, bookId)
            .setParameter(2, text)
            .executeUpdate();
    }
}

package org.example.bookshelf.service;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.transaction.Transactional;

@ApplicationScoped
public class LoanService {

    @PersistenceContext
    private EntityManager em;

    @Transactional
    public void rename(long id, String title) {
        BookEntity book = em.find(BookEntity.class, id);
        book.setTitle(title);
    }

    @Transactional(Transactional.TxType.REQUIRES_NEW)
    public void audit(String message) {
        em.createNativeQuery("insert into audit(message) values (?1)")
            .setParameter(1, message)
            .executeUpdate();
    }
}
ReviewBean.java — a backing bean that calls both
package org.example.bookshelf.web;

import jakarta.ejb.EJB;
import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import org.example.bookshelf.service.LoanService;
import org.example.bookshelf.service.ReviewService;

@Named
@RequestScoped
public class ReviewBean {

    @EJB
    private ReviewService reviews;

    @Inject
    private LoanService loans;

    private String text;

    public String save() {
        reviews.add(1, text);
        loans.audit("review added");
        return null;
    }

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }
}

The rules that the sample illustrates:

  • The transaction belongs to the service, not to the view bean. A @Transactional method (or any method of a stateless EJB, which is transactional by default) is where a unit of work starts and ends. The backing bean calls the method once per user action and does not carry a transaction across the lifecycle phases, which would mean holding one open between requests.

  • @Transactional is an interceptor binding. It acts only on calls that arrive through the container proxy (see Cross-Cutting Concerns with Interceptors), and it needs a transaction manager. On an application server there is one. On Tomcat you must supply a JTA implementation or use resource-local transactions in the service. This page did not run a JTA transaction on Tomcat.

  • Rollback follows the exception type. By the specification, runtime exceptions mark the transaction for rollback and checked exceptions do not, unless rollbackOn and dontRollbackOn change it. A service that throws a checked exception to report a business error commits, which is rarely wanted; rollbackOn fixes it.

  • REQUIRES_NEW suspends the caller’s transaction. The audit method above commits independently of the transaction that called it, which is the usual way to write an audit trail that survives a rollback.

  • Return what the view needs. Entities loaded in the service’s transaction and rendered later, when the transaction is over, can fail on lazily loaded associations. Return a record or an already initialised object. The persistence page goes further on this.

  • @EJB and @Inject. @EJB is the classic injection point for an enterprise bean and is what the sample uses. On an application server an EJB can also be a CDI bean and be injected with @Inject; either way the field is a proxy, so the call is intercepted.

On Spring Boot (JoinFaces) and on Quarkus the transaction annotations and the container come from those frameworks and not from the Jakarta EE server; the Spring Boot with JoinFaces and Quarkus pages show the wiring (the servers page covers the Jakarta EE servers).

The Removed @ManagedBean

Faces 2.0 was written when Java EE had no common bean model, so it carried its own: a class annotated @ManagedBean in the package javax.faces.bean, with scope annotations in the same package (request, view, session, application, none and custom), @ManagedProperty for dependencies, and an equivalent managed-bean element in faces-config.xml. The Jakarta EE Tutorial in its current edition (the Jakarta EE 10 tutorial) still describes the XML form.

The specification’s change log records how it ended:

  • 2.2 introduced the CDI-based @ViewScoped and noted that the javax.faces.bean package would be deprecated in the next version;

  • 2.3 added the CDI replacement for @ManagedProperty and the injection of Faces objects;

  • 4.0 listed "Remove native Managed Beans (@ManagedBean and related)".

What a legacy application meets in 4.1:

  • The annotations do not exist. The 4.1 API jar (jakarta.faces-api 4.1.2) has no bean package at all, and the Jakarta Annotations 3.0 jar has no jakarta.annotation.ManagedBean either, so code that imports them stops compiling.

  • The XML element is ignored silently. A faces-config.xml at version 4.1 that still declares a managed-bean element produced no error and no warning on either implementation; the bean was simply absent, and #{legacy.hello} rendered an empty string. After an upgrade, grep for managed-bean in every faces-config.xml, including the ones inside libraries.

  • Names and scopes need translating. The table below maps each piece.

Native facility (removed in 4.0) CDI equivalent in 4.1

@ManagedBean, @ManagedBean(name = "x")

@Named, @Named("x"). The default name is the same rule, the class name with a lower-case first letter.

javax.faces.bean.RequestScoped, SessionScoped, ApplicationScoped

jakarta.enterprise.context.RequestScoped, SessionScoped, ApplicationScoped

javax.faces.bean.ViewScoped

jakarta.faces.view.ViewScoped (the bean must now be Serializable)

@NoneScoped

@Dependent

@CustomScoped

a custom CDI scope or @ConversationScoped, depending on the intent

@ManagedBean(eager = true) on an application-scoped bean

an observer of @Initialized(ApplicationScoped.class) on an @ApplicationScoped bean (the start-up probe above used this event)

@ManagedProperty(value = "#{other}") with a setter, to inject another bean

@Inject Other other;

@ManagedProperty(value = "#{param.id}") with a setter, for an EL value

@Inject @ManagedProperty("#{param.id}") on a field

<managed-bean> in faces-config.xml

annotations on the class, beans.xml, and producers for configured values

@PostConstruct, @PreDestroy

unchanged, from Jakarta Annotations

Migrating One Bean

The legacy form, in the style of the Faces 2.x books and tutorials, is shown for comparison only. It does not compile on Faces 4.1:

BookBean.java — legacy, does not compile on Faces 4.1
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ManagedProperty;
import javax.faces.bean.ViewScoped;

@ManagedBean(name = "catalogue")
@ViewScoped
public class BookBean implements java.io.Serializable {

    @ManagedProperty("#{bookService}")
    private BookService service;

    public void setService(BookService service) {
        this.service = service;
    }
    // ...
}

The migrated bean is the CatalogueBean at the top of this page. The four changes are the imports, @Named in place of @ManagedBean, a field with @Inject in place of the setter and @ManagedProperty, and a service that is itself a CDI bean (BookService is @ApplicationScoped; it used to be another managed bean or a hand-built singleton). The order that works in practice:

  1. Replace the imports, bean by bean. The compiler finds every remaining use of the old package. The faces-config.xml entries are the part the compiler cannot find.

  2. Make sure the archive has beans.xml and that every bean has a scope annotation (Bean Discovery and beans.xml).

  3. Replace setter injection by @Inject. Replace @ManagedProperty of another bean by @Inject of that bean, and keep @ManagedProperty (the Jakarta one) only for EL values.

  4. Make view-scoped and session-scoped beans Serializable and check their fields (What a Backing Bean Must Satisfy).

  5. Move code that ran in @ManagedBean(eager = true) constructors to an @Initialized(ApplicationScoped.class) observer.

  6. Deploy, open every page, and watch for blank output where a value should be: it is the signature of a bean that was not discovered or that was renamed.

The migrating to Jakarta Faces page collects the other removals that arrive in the same upgrade.

Appendix: The Lifecycle Probe

The sequence of Where a Bean Is Used in the Lifecycle came from a request-scoped bean that logs its life. It is listed here so that you can repeat the experiment on your own server.

ProbeBean.java — logs construction, phases and destruction
package org.example.bookshelf.web;

import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import jakarta.enterprise.context.RequestScoped;
import jakarta.faces.context.FacesContext;
import jakarta.inject.Inject;
import jakarta.inject.Named;

@Named
@RequestScoped
public class ProbeBean {

    @Inject
    FacesContext facesContext;

    private String name = "";

    public ProbeBean() {
        System.out.println("PROBE constructor");
    }

    @PostConstruct
    void init() {
        System.out.println("PROBE postConstruct phase=" + facesContext.getCurrentPhaseId());
    }

    public String getName() {
        System.out.println("PROBE getName phase=" + facesContext.getCurrentPhaseId());
        return name;
    }

    public void setName(String name) {
        System.out.println("PROBE setName phase=" + facesContext.getCurrentPhaseId());
        this.name = name;
    }

    public String save() {
        System.out.println("PROBE save phase=" + facesContext.getCurrentPhaseId());
        return null;
    }

    @PreDestroy
    void destroy() {
        System.out.println("PROBE preDestroy");
    }
}
probe.xhtml — one input and one button
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="jakarta.faces.html">
<h:head><title>Probe</title></h:head>
<h:body>
  <h:form id="f">
    <h:inputText id="name" value="#{probeBean.name}"/>
    <h:commandButton id="save" value="save" action="#{probeBean.save}"/>
    <h:inputText id="draft" value="#{reviewDraftBean.comment}"/>
  </h:form>
</h:body>
</html>

The initial GET logged construction, @PostConstruct and the getter in Render Response, then @PreDestroy. The postback with f:name=Ann and f:save=save logged, in this order: constructor, @PostConstruct in Process Validations, setName in Update Model Values, save in Invoke Application, getName in Render Response and @PreDestroy. The phase is reported as RENDER_RESPONSE 6 on Mojarra and RENDER_RESPONSE(6) on MyFaces; the order was the same.

Common Mistakes

  • A @Named bean without a scope annotation under annotated discovery. The name resolves to nothing and an output expression shows blank. Add a scope, or use a stereotype.

  • No beans.xml on Tomcat. None of the beans is found; every page renders blanks and the first action fails with Target Unreachable.

  • The old package. An IDE that auto-imports javax.faces.bean classes from a library on the class path, or a copy of a book’s example, gives a bean that never exists. On 4.1 the package is gone, so the compiler complains. Read the imports first.

  • Queries or side effects in getters. A getter runs in each render and for each reference. Load in @PostConstruct of a view bean or in an action.

  • Faces types in the service layer. A service that needs FacesContext or FacesMessage cannot be used from anywhere else. Let it throw its own exception and let the view bean convert it.

  • Holding a FacesContext, UIComponent or request-bound object in a long-lived bean field. Inject the proxy (@Inject FacesContext) or fetch the object inside the method.

  • @ManagedProperty in a long-lived bean. The value is fixed at creation. Use a method that reads the current request, or f:viewParam.

  • A non-serialisable dependency in a view-scoped, session-scoped or conversation-scoped bean. Deployment fails with WELD-001413. Make the dependency serialisable, or inject a normal-scoped bean (a proxy is serialisable).

  • Removing the no-argument constructor of a normal-scoped bean. Deployment fails with WELD-001435.

  • A bean named like an implicit object (request, session, param, flash, view, component, cc, flow). Choose a different name.

  • Business logic and transactions inside the view bean. They cannot be reused or tested. Move them into a service.

  • Page state in a session-scoped bean. The scopes page explains why a view-scoped bean (or a window-scoped one) is right.

  • @Transactional on a method called from the same class. The interceptor is skipped (stated by the interceptor model, not run here); call it through another bean.

Mojarra and MyFaces Compared

Everything on this page is CDI, so the two implementations behave alike. Every scenario in the examples ran with the same result on Mojarra 4.1.17 and MyFaces Core 4.1.4: discovery, naming, injection of every object and map, @ManagedProperty (including the failure for id=abc), the alternative, the producer, the interceptor, the event, the ignored managed-bean element and the lifecycle order. The differences were cosmetic:

Observation Mojarra 4.1.17 MyFaces 4.1.4

ContextNotActiveException when a FacesContext is used outside a request

names jakarta.enterprise.context.RequestScoped

names org.apache.myfaces.cdi.FacesScoped

FacesContext.getCurrentPhaseId() as text

RENDER_RESPONSE 6

RENDER_RESPONSE(6)

Whitespace around the text of a FacesMessage in the <li>

a tab character inside the <li> before the text

no such character

Posting a form by hand (a script or curl)

the hidden form field named after the form id is enough

also needs the hidden form_SUBMIT field

The CDI container underneath was Weld in both runs. On a Jakarta EE server the CDI container is the server’s own, and the Faces 4.1 requirement is the same (see the servers page).

What Changed per Version

Version Change

2.0

The native managed bean facility: @ManagedBean, the scope annotations of javax.faces.bean, @ManagedProperty and the managed-bean element in faces-config.xml. This is what the 2004 to 2010 books describe, or the XML-only form before it.

2.2

CDI-based @ViewScoped (jakarta.faces.view.ViewScoped today) and @FlowScoped; the change log says javax.faces.bean will be deprecated in the next version.

2.3

Injection of Faces artefacts with @Inject and the map qualifiers; the CDI replacement for @ManagedProperty; CDI evaluates the Faces implicit objects; managed converters, validators and behaviours; @FacesConfig to activate the CDI features. The native facility is deprecated.

3.0

The namespace move from javax.* to jakarta.*. The names of the annotations on this page are the same, with the new prefix.

4.0

The native managed beans are removed. Every Faces qualifier gets an AnnotationLiteral (Qualifier Literals for Programmatic Lookup). @FacesConfig only guarantees that Faces and its CDI features are activated when there is no faces-config.xml and the servlet is not registered; its version element is deprecated and has no effect.

4.1

The current Flow can be injected (@Inject Flow). Everything else on this page is unchanged. Faces 4.1 sits on CDI 4.1 in Jakarta EE 11 (an empty beans.xml means annotated since CDI 4.0).

The 5.0 milestones (5.0.0-M9, 2026-09-18) were not checked for changes to the bean model, and anything about Faces 5.0 is upcoming, not current behaviour.

Where the Books Are Outdated

Every book in the bibliography of this section was written before CDI was the bean model of Faces, so the backing bean chapters all need the translation above. The ideas survive: a bean is the model and controller of a view, it holds properties and action methods, and the view reaches it by name through the Expression Language.

  • JSF 1.x books (Mann, 2005; Dudney et al., 2004) register beans in faces-config.xml with managed-bean elements, name their scope there, and wire dependencies with managed-property elements. Today the element is ignored (the run above) and the annotations and beans.xml do the work.

  • JSF 2.x books (Burns, Schalk and Griffin, 2010; Leonard, 2010; Çalışkan and Varaksin, 2015) use @ManagedBean, @ManagedProperty with a setter and the scope annotations of javax.faces.bean. In 4.1 these are @Named, @Inject and the scopes of jakarta.enterprise.context and jakarta.faces.view, with the javax prefix replaced by jakarta throughout.

  • Spring and EJB integration (Leonard) relies on a Spring variable resolver or on a JNDI lookup to reach the service layer. A CDI bean injects an EJB or a CDI service directly, and Spring Boot with JoinFaces exposes Spring beans to EL through its own resolver.

  • Static FacesContext.getCurrentInstance() everywhere. The books reach the context, the external context and the maps through static calls and getter chains. The injection of 2.3 is shorter and easier to test, and it is what current code does.

  • Session-scoped beans as the default. Several older examples keep page state in session beans because the request scope was too short and the view scope did not exist before 2.0. With the view scope and the window scope (see the scopes page) they are no longer needed for page state.

Use the books for the idea of a backing bean and for the component and lifecycle chapters, and this page for what to type.

Source: Kito D. Mann, JavaServer Faces in Action (Manning, 2005, ISBN 978-1-932394-12-2); Bill Dudney, Jonathan Lehr, Bill Willis and LeRoy Mattingly, Mastering JavaServer Faces (Wiley, 2004, ISBN 978-0-471-46207-1); Ed Burns, Chris Schalk and Neil Griffin, JavaServer Faces 2.0: The Complete Reference (McGraw-Hill, 2010, ISBN 978-0-07-162509-8); Anghel Leonard, JSF 2.0 Cookbook (Packt, 2010, ISBN 978-1-847199-52-2); Mert Çalışkan and Oleg Varaksin, PrimeFaces Cookbook, Second Edition (Packt, 2015, ISBN 978-1-78439-342-7)

References