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 |
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) |
|
The converted and validated value is written to the property. |
Invoke Application (phase 5) |
|
The action method named by the button runs. |
Render Response (phase 6) |
|
The page is rendered; the getter supplies the value shown. |
end of the request |
|
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.
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));
}
}
Source: Jakarta EE Tutorial: Giving Beans EL Names and Jakarta EE Tutorial: Managed Beans in Jakarta Faces Technology
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:
<!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:
-
@ViewScopedhere isjakarta.faces.view.ViewScoped, not a scope fromjakarta.enterprise.context. The bean survives postbacks of the same view, which is why the list loaded in@PostConstructis still there when a button is pressed. Because the scope is passivating, the bean implementsSerializableand 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
BookServicefor the list and for the changes. Layering: View Bean, Service, Repository explains why. -
@Inject FacesContextgives 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:
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 |
|---|---|---|
|
found |
not found: |
|
found |
found, as a |
empty |
found |
not found: since CDI 4.0 an empty file means |
no |
not found: no bean of the WAR resolved, and a button referring to one failed with |
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@Namedworks underalland silently disappears underannotated, 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. -
allmakes 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,@ApplicationScopedand the other normal scopes hand out a client proxy, not the instance, so the class must be proxyable: notfinal, with nofinalmethods 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 withWELD-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,@ConversationScopedandjakarta.faces.view.ViewScopedare passivating. A bean in such a scope must beSerializable, and every dependency injected into it must be passivation capable too. Adding@Inject Helper helperto the view-scoped catalogue bean, whereHelperwas a@Dependentclass that was notSerializable, stopped deployment withWELD-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/isXandsetX; EL resolves#{bean.x}through them, not through the field. -
Lifecycle callbacks are
@PostConstructand@PreDestroy, from Jakarta Annotations. Use@PostConstructfor 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.
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:
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:
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:
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):
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);
}
}
Source: Jakarta CDI 4.1 specification, Observer methods and Jakarta EE Tutorial: Jakarta Contexts and Dependency Injection: Advanced Topics
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:
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:
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 |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Source: Jakarta Faces 4.1 specification, 5.6.1 Jakarta Faces Objects Valid for @Inject Injection and RequestParameterMap API documentation
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.
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());
}
}
<!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
Source: FacesContext API documentation
What the run showed about injection of Faces objects:
-
Each one is a contextual reference, usually a proxy. Injecting
FacesContextinto 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. AFacesContextcopied out ofgetCurrentInstance()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 injectedFacesContextat start-up failed with aContextNotActiveExceptionon both implementations. The exception names the scope (jakarta.enterprise.context.RequestScopedon Mojarra, the internalorg.apache.myfaces.cdi.FacesScopedon MyFaces). Start-up code, scheduled jobs and asynchronous tasks cannot touch the Faces objects; pass them the data instead. -
@Inject HttpServletRequestalso worked on Tomcat with Weld, although the specification lists onlyHttpSession. That is a feature of the servlet CDI integration, not of Faces, and it is not available where that integration is absent.@Inject Principalwas not satisfied on Tomcat with Weld (deployment failed withWELD-001408: Unsatisfied dependencies for type Principal), so read the user fromexternalContext.getUserPrincipal()(it printednullfor an anonymous request) or from the Jakarta SecuritySecurityContextwhere available. -
The map qualifiers return live maps. The
@SessionMapfield of the example, filled intouchSession, produced the messageVisits: 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:
@ViewMapis 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:
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");
}
}
Source: RequestMap.Literal API documentation and Jakarta Faces 4.1 specification, B.2 Changes between 4.0 and 3.0
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:
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);
}
}
<!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 |
|---|---|
|
the field is |
|
an empty string (not |
|
the request fails while the bean is created: |
|
the bean kept |
|
|
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:
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();
}
}
Source: Jakarta EE Tutorial: Injecting Objects by Using Producer Methods and Jakarta CDI 4.1 specification, Declaring a producer method
#{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.
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();
}
}
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
@Transactionalmethod (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. -
@Transactionalis 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
rollbackOnanddontRollbackOnchange it. A service that throws a checked exception to report a business error commits, which is rarely wanted;rollbackOnfixes it. -
REQUIRES_NEWsuspends the caller’s transaction. Theauditmethod 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.
-
@EJBand@Inject.@EJBis 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
@ViewScopedand noted that thejavax.faces.beanpackage would be deprecated in the next version; -
2.3 added the CDI replacement for
@ManagedPropertyand the injection of Faces objects; -
4.0 listed "Remove native Managed Beans (@ManagedBean and related)".
Source: Jakarta Faces 4.1 specification, B.5 Changes between 2.1 and 2.2 and B.2 Changes between 4.0 and 3.0
What a legacy application meets in 4.1:
-
The annotations do not exist. The 4.1 API jar (
jakarta.faces-api4.1.2) has nobeanpackage at all, and the Jakarta Annotations 3.0 jar has nojakarta.annotation.ManagedBeaneither, so code that imports them stops compiling. -
The XML element is ignored silently. A
faces-config.xmlat version 4.1 that still declares amanaged-beanelement 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 formanaged-beanin everyfaces-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 |
|---|---|
|
|
|
|
|
|
|
|
|
a custom CDI scope or |
|
an observer of |
|
|
|
|
|
annotations on the class, |
|
unchanged, from Jakarta Annotations |
Source: Jakarta EE Tutorial: Using the managed-bean Element (still describes the removed facility) and Jakarta Annotations 3.0
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:
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:
-
Replace the imports, bean by bean. The compiler finds every remaining use of the old package. The
faces-config.xmlentries are the part the compiler cannot find. -
Make sure the archive has
beans.xmland that every bean has a scope annotation (Bean Discovery and beans.xml). -
Replace setter injection by
@Inject. Replace@ManagedPropertyof another bean by@Injectof that bean, and keep@ManagedProperty(the Jakarta one) only for EL values. -
Make view-scoped and session-scoped beans
Serializableand check their fields (What a Backing Bean Must Satisfy). -
Move code that ran in
@ManagedBean(eager = true)constructors to an@Initialized(ApplicationScoped.class)observer. -
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.
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");
}
}
Source: FacesContext API documentation
<!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
@Namedbean without a scope annotation underannotateddiscovery. The name resolves to nothing and an output expression shows blank. Add a scope, or use a stereotype. -
No
beans.xmlon Tomcat. None of the beans is found; every page renders blanks and the first action fails withTarget Unreachable. -
The old package. An IDE that auto-imports
javax.faces.beanclasses 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
@PostConstructof a view bean or in an action. -
Faces types in the service layer. A service that needs
FacesContextorFacesMessagecannot be used from anywhere else. Let it throw its own exception and let the view bean convert it. -
Holding a
FacesContext,UIComponentor request-bound object in a long-lived bean field. Inject the proxy (@Inject FacesContext) or fetch the object inside the method. -
@ManagedPropertyin a long-lived bean. The value is fixed at creation. Use a method that reads the current request, orf: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.
-
@Transactionalon 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 |
|---|---|---|
|
names |
names |
|
|
|
Whitespace around the text of a |
a tab character inside the |
no such character |
Posting a form by hand (a script or |
the hidden |
also needs the hidden |
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: |
2.2 |
CDI-based |
2.3 |
Injection of Faces artefacts with |
3.0 |
The namespace move from |
4.0 |
The native managed beans are removed. Every Faces qualifier gets an |
4.1 |
The current |
Source: Jakarta Faces 4.1 specification, B.1 Changes between 4.1 and 4.0 and FacesConfig API documentation
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.xmlwithmanaged-beanelements, name their scope there, and wire dependencies withmanaged-propertyelements. Today the element is ignored (the run above) and the annotations andbeans.xmldo the work. -
JSF 2.x books (Burns, Schalk and Griffin, 2010; Leonard, 2010; Çalışkan and Varaksin, 2015) use
@ManagedBean,@ManagedPropertywith a setter and the scope annotations ofjavax.faces.bean. In 4.1 these are@Named,@Injectand the scopes ofjakarta.enterprise.contextandjakarta.faces.view, with thejavaxprefix replaced byjakartathroughout. -
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
-
Jakarta EE Tutorial: Developing with Jakarta Faces Technology (the tutorial
currentis the Jakarta EE 10 one) -
Jakarta EE Tutorial: Introduction to Contexts and Dependency Injection
-
Jakarta EE Tutorial: Contexts and Dependency Injection: Advanced Topics
-
Jakarta Faces 4.1 specification, 5.6.1 Jakarta Faces Objects Valid for @Inject Injection
-
Jakarta Faces 4.1 specification, 5.6.2 Expression Language Resolution
-
Mann: JavaServer Faces in Action, Manning, 2005, publisher page
-
Dudney, Lehr, Willis, Mattingly: Mastering JavaServer Faces, Wiley, 2004, publisher page
-
Burns, Schalk, Griffin: JavaServer Faces 2.0: The Complete Reference, McGraw-Hill, 2010, publisher page
-
Leonard: JSF 2.0 Cookbook, Packt, 2010, publisher page
-
Çalışkan, Varaksin: PrimeFaces Cookbook, Second Edition, Packt, 2015, publisher page
-
Jakarta EE Tutorial: Managed Beans in Jakarta Faces Technology
-
Jakarta CDI 4.1 specification, Default bean name for a managed bean
-
Jakarta EE Tutorial: Using the @PostConstruct and @PreDestroy Annotations
-
Jakarta Faces 4.1 specification, B.2 Changes between 4.0 and 3.0
-
Jakarta EE Tutorial: Injecting Objects by Using Producer Methods
-
Jakarta Transactions 2.0 specification, the @Transactional annotation
-
Jakarta Faces 4.1 specification, B.5 Changes between 2.1 and 2.2
-
Jakarta EE Tutorial: Using the managed-bean Element (still describes the removed facility)
-
Jakarta EE Tutorial: The Lifecycle of a Jakarta Faces Application
-
Jakarta Faces 4.1 specification, B.1 Changes between 4.1 and 4.0