Java EE / Jakarta EE
Jakarta Faces (JSF) 4: componentes, ciclo de vida e backing beans CDI
Intermediário11 min de leituraAtualizado
Esta página foi traduzida do inglês com a ajuda de IA e conferida com o original. Ler a versão em inglês
Jakarta Faces (JavaServer Faces até a mudança de nome para Jakarta) é o framework web baseado em componentes da plataforma, e uma parcela enorme do software corporativo ainda depende dele. Este guia explica o ciclo de vida de uma requisição em seis fases, como as páginas Facelets se ligam a backing beans CDI por meio da Expression Language, como funcionam conversão, validação, navegação e Ajax, e o que o Faces 4.0 removeu, para que você possa manter aplicações Faces existentes com segurança e decidir quando ele ainda é a ferramenta certa.
O que você vai aprender
- Explicar o ciclo de vida de uma requisição Faces e a diferença entre uma requisição inicial e um postback
- Construir uma página com templates Facelets, componentes padrão e um backing bean CDI
- Escolher o escopo de bean certo e entender por que beans @ViewScoped precisam ser serializáveis
- Aplicar converters, validadores, Bean Validation e atualizações parciais com Ajax
- Identificar o que mudou no Faces 4.0 e por que o Faces é considerado legado para novos front-ends
O Jakarta Faces encara interfaces web de um jeito bem diferente de qualquer coisa construída com um framework JavaScript: o servidor mantém uma árvore de componentes para cada página, o navegador envia os formulários de volta para essa árvore e o framework converte, valida e aplica os valores aos beans Java antes de renderizar a próxima visão. Se você vem de Spring MVC ou de uma stack REST com React, esse modelo com estado explica tanto por que o Faces era tão produtivo para equipes só de Java (sem JavaScript, sem contratos JSON, binding tipado com o seu domínio) quanto por que ele saiu de moda quando single-page applications e escalabilidade horizontal viraram regra. Muito software de negócio ainda roda sobre ele, então o objetivo aqui é fluência: saber o que acontece em cada requisição para conseguir depurá-la.
Onde o Faces se encaixa e o que ele é hoje
O Faces é um servlet. O FacesServlet é mapeado automaticamente para *.xhtml (e para /faces/*, *.faces e *.jsf, por causa de links antigos) assim que o contêiner detecta que o Faces está em uso; assim, uma requisição para /shop/orders.xhtml passa pelo contêiner de servlets descrito no guia de servlets e chega ao Faces. As páginas são escritas em Facelets, um dialeto de XHTML; os backing beans são beans CDI comuns; a Expression Language (EL) liga os dois.
O histórico de versões importa, porque a API que você vê em uma base de código revela a idade dela:
| Versão | Ano | Destaques |
|---|---|---|
| JSF 1.0 / 1.2 | 2004 / 2006 | Visões JSP, faces-config.xml para tudo |
| JSF 2.0 | 2009 | Facelets como tecnologia de visão padrão, anotações, navegação implícita, Ajax embutido, view scope |
| JSF 2.2 | 2013 | Atributos pass-through pensados para o HTML5, Faces Flows, upload de arquivos, visões stateless |
| JSF 2.3 | 2017 | Integração completa com CDI (@Inject FacesContext), <f:websocket>, suporte a data e hora do java.time |
| Jakarta Faces 3.0 | 2020 | Mudança de namespace para jakarta.faces, sem mudança funcional |
| Jakarta Faces 4.0 | 2022 | Remoção de @ManagedBean e dos escopos de jakarta.faces.bean, fim do suporte a visões JSP, namespaces XML curtos (jakarta.faces.html), mapeamento automático sem extensão, @ClientWindowScoped |
Existem duas implementações: Eclipse Mojarra (a implementação compatível, empacotada no GlassFish, WildFly e Payara) e Apache MyFaces (empacotada no Open Liberty e no TomEE). Bibliotecas de componentes (com o PrimeFaces à frente) fornecem os widgets ricos que a maioria das aplicações de fato usa, e o OmniFaces cobre as lacunas com seus utilitários. O Tomcat não traz o Faces: você mesmo adiciona a implementação e um contêiner CDI, mais um motivo para preferir um servidor Web Profile ao trabalhar com Faces.
O ciclo de vida da requisição
Entender as seis fases é a coisa mais útil que você pode aprender sobre o Faces, porque quase toda pergunta do tipo "não funciona" na verdade é "qual fase parou, e por quê".
- Restore View: em um postback, a árvore de componentes salva da página é restaurada (da sessão, por padrão); em um
GETinicial, uma árvore nova é construída e o ciclo de vida pula direto para Render Response. - Apply Request Values: cada componente de entrada lê a string bruta enviada a partir dos parâmetros da requisição. Componentes cujo atributo
renderedé false são ignorados, e os componentes de ação enfileiram seus eventos aqui. - Process Validations: as strings enviadas são convertidas para o tipo de destino e depois validadas. Qualquer falha adiciona uma
FacesMessage, marca o componente como inválido e faz o ciclo de vida pular para Render Response: o modelo nunca é tocado e nenhum método de ação é executado. - Update Model Values: os valores convertidos e válidos são gravados nas propriedades do bean referenciadas por
value="#{bean.property}". - Invoke Application: os métodos de ação e os action listeners são executados; o valor de retorno da ação decide a navegação.
- Render Response: a visão é renderizada em HTML e seu estado é salvo para o próximo postback.
Definir immediate="true" em uma entrada ou em um comando move seu processamento para a fase Apply Request Values; é assim que botões "Cancelar" escapam da validação do resto do formulário. Adicionar <h:messages globalOnly="false" /> a uma página durante o desenvolvimento exibe as falhas de conversão e validação que, de outra forma, ficariam invisíveis.
Uma página e seu backing bean
Um formulário típico: uma página de busca de pedidos com um filtro, uma tabela de resultados e um botão.
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="jakarta.faces.html"
xmlns:f="jakarta.faces.core"
xmlns:ui="jakarta.faces.facelets">
<ui:composition template="/WEB-INF/templates/layout.xhtml">
<ui:define name="title">Orders</ui:define>
<ui:define name="content">
<h:form id="search">
<h:messages />
<h:outputLabel for="customer" value="Customer" />
<h:inputText id="customer" value="#{orderSearch.customer}" required="true"
requiredMessage="Enter a customer name" />
<h:selectOneMenu id="status" value="#{orderSearch.status}">
<f:selectItem itemLabel="Any" noSelectionOption="true" />
<f:selectItems value="#{orderSearch.statuses}" />
</h:selectOneMenu>
<h:commandButton value="Search" action="#{orderSearch.search}">
<f:ajax execute="@form" render="@form results" />
</h:commandButton>
</h:form>
<h:dataTable id="results" value="#{orderSearch.results}" var="o" rendered="#{not empty orderSearch.results}">
<h:column><f:facet name="header">Id</f:facet>#{o.id}</h:column>
<h:column><f:facet name="header">Total</f:facet>
<h:outputText value="#{o.total}"><f:convertNumber type="currency" currencyCode="EUR" /></h:outputText>
</h:column>
<h:column><f:facet name="header">Placed</f:facet>
<h:outputText value="#{o.placedAt}"><f:convertDateTime type="localDate" /></h:outputText>
</h:column>
</h:dataTable>
</ui:define>
</ui:composition>
</html>
O backing bean é um bean CDI. @Named o expõe à EL com o nome orderSearch (o nome da classe com a inicial minúscula), e @ViewScoped o mantém vivo enquanto o usuário permanecer nesta visão, inclusive entre postbacks Ajax:
package shop.web;
import java.io.Serializable;
import java.util.List;
import jakarta.faces.view.ViewScoped;
import jakarta.inject.Inject;
import jakarta.inject.Named;
@Named
@ViewScoped // jakarta.faces.view.ViewScoped, not the removed jakarta.faces.bean one
public class OrderSearch implements Serializable {
@Inject
OrderService orderService; // a CDI or enterprise bean; transactions live there, not here
private String customer;
private OrderStatus status;
private List<Order> results = List.of();
public void search() { // no return value: stay on the same view
results = orderService.find(customer, status);
}
public OrderStatus[] getStatuses() { return OrderStatus.values(); }
// getters and setters for customer, status, results
}
Repare no que o bean não faz: ele não abre transações, não monta SQL e não guarda o EntityManager. Mantenha o backing bean como modelo de visão e delegue para um serviço; o guia de session beans mostra como é esse serviço.
Escolhendo um escopo
| Escopo | Tempo de vida | Uso típico | Ressalva |
|---|---|---|---|
@RequestScoped | Uma requisição | Páginas sem estado, formulários simples sem Ajax | Os valores se perdem entre postbacks; renderização condicional baseada no bean quebra |
@ViewScoped | Uma visão, entre postbacks para a mesma página | A maioria das páginas com formulários, tabelas e Ajax | Precisa ser Serializable; estado armazenado por visão |
@FlowScoped("checkout") | Um fluxo de várias páginas com entrada e saída definidas | Assistentes (wizards) | Exige definição de fluxo |
@SessionScoped | A sessão HTTP | Usuário logado, preferências | Aumenta o consumo de memória; nunca para dados de formulário de uma página |
@ApplicationScoped | A aplicação | Dados de referência, caches | Precisa ser thread-safe |
@ClientWindowScoped (4.0) | Uma aba ou janela do navegador | Estado específico da aba | Depende do modo client-window |
Beans com escopo de visão e de sessão são passivation-capable: o contêiner pode serializá-los em disco ou replicá-los para outro nó, então todo campo precisa ser serializável ou transient. Um proxy de enterprise bean injetado não tem problema; um EntityManager injetado, sim.
Facelets: templates, includes e componentes compostos
Páginas Facelets se compõem. Um template declara pontos de inserção com <ui:insert name="content" />; uma página usa <ui:composition template="…"> e os preenche com <ui:define>. <ui:include src="…"> traz fragmentos, <ui:param> passa valores para eles e <ui:repeat> itera sem gerar uma tabela. Tudo o que fica fora de <ui:composition> em uma página que usa template é ignorado, o que surpreende quem adiciona marcação no topo do arquivo.
Componentes compostos permitem empacotar um fragmento de marcação com sua própria interface em uma biblioteca de recursos, de modo que <shop:address value="#{customer.address}" /> vira um componente reutilizável sem escrever Java. Para qualquer coisa mais complexa (renderização personalizada, comportamento em JavaScript), bibliotecas de componentes como o PrimeFaces geralmente já fizeram o trabalho.
Dois detalhes da EL economizam horas. #{…} é uma expressão diferida, que o Faces avalia durante o ciclo de vida e na qual também pode gravar; ${…} é avaliada imediatamente durante a construção da página e é somente leitura. Em Facelets, use #{…} em todo lugar. Além disso, getters podem ser chamados várias vezes por requisição, uma vez por fase que precisa do valor, então um getter deve ser barato e sem efeitos colaterais: carregue os dados em um método de ação ou em um método @PostConstruct e guarde-os em um campo.
Conversão, validação e mensagens
Toda entrada passa por conversão (de string para tipo) e validação (regras sobre o tipo) antes de chegar ao modelo. Existem converters padrão para números e datas (<f:convertNumber>, <f:convertDateTime> com suporte a java.time desde a 2.3), e o Faces resolve converters automaticamente para tipos primitivos, enums e BigDecimal. Para os seus próprios tipos, implemente Converter<T> e registre-o com @FacesConverter(forClass = Money.class); a partir do Faces 2.3, converters e validadores podem ser beans CDI com @FacesConverter(managed = true), o que permite injetar serviços neles.
A validação tem três camadas, aplicadas nesta ordem:
required="true": verificado antes da conversão; strings vazias só são tratadas como null por padrão sejakarta.faces.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULLestiver definido, o que quase toda aplicação deveria fazer.- Validadores do Faces:
<f:validateLength>,<f:validateLongRange>,<f:validateRegex>,<f:validateBean>e classes@FacesValidatorpersonalizadas. - Bean Validation: restrições como
@NotBlankou@Size(max = 100)na propriedade do bean são validadas automaticamente quando há um provedor de Bean Validation, o que sempre acontece em um servidor Jakarta EE. Assim, as regras ficam em um só lugar, compartilhadas com sua camada REST e suas entidades JPA.
As falhas viram objetos FacesMessage associados a um id de componente; <h:message for="customer" /> exibe uma, <h:messages /> exibe todas. O código da aplicação pode adicionar as suas com FacesContext.getCurrentInstance().addMessage(...), e as mensagens sobrevivem a um redirecionamento se você chamar context.getExternalContext().getFlash().setKeepMessages(true).
Navegação e o padrão Post-Redirect-Get
Desde a 2.0 a navegação é implícita: um método de ação que retorna "orders" renderiza orders.xhtml; retornar null ou void renderiza de novo a visão atual. Depois de um postback simples, porém, a página renderizada e a URL do navegador divergem: a URL continua mostrando a página que foi enviada, e atualizar o navegador reenvia o formulário. Acrescente ?faces-redirect=true ao outcome (return "orders?faces-redirect=true";) para enviar um redirecionamento e obter um GET limpo; adicione &includeViewParams=true para levar junto os valores de <f:viewParam>. Blocos <navigation-rule> explícitos no faces-config.xml continuam suportados, e você vai encontrá-los em código da era JSF 1.x, mas código novo raramente precisa deles.
Para páginas que podem ir para os favoritos e carregam dados a partir da URL, <f:metadata><f:viewParam name="id" value="#{orderView.id}" /><f:viewAction action="#{orderView.load}" /></f:metadata> vincula um parâmetro de consulta e executa um método no GET inicial: é o equivalente, no Faces, a um método de controller GET.
Ajax sem JavaScript
<f:ajax> aninhado em um componente transforma seu evento padrão (clique, nos botões; mudança, nas entradas) em um postback parcial. execute indica os componentes cujos valores são enviados e processados (@this, @form, @all ou ids), e render indica os que são renderizados novamente a partir da resposta. O ciclo de vida inteiro continua rodando no servidor para a subárvore executada, então validação, atualização do modelo e método de ação se comportam exatamente como em um postback completo; só o transporte e a atualização do DOM mudam. Os ids em render precisam ser resolvidos em relação ao naming container (um componente dentro de h:form id="search" tem o client id search:customer), e um id que não existe falha na renderização com uma mensagem clara, enquanto um id que existe mas não é renderizado (porque rendered="false") não atualiza nada e não avisa. Envolva regiões renderizadas condicionalmente em um <h:panelGroup id="wrapper"> sempre renderizado e aponte para ele.
Por que o Faces é legado, e o que ainda pesa a favor dele
O modelo tem custos reais. O estado por visão guardado na sessão limita a escalabilidade horizontal, a menos que você use salvamento de estado no cliente ou sticky sessions. Toda interação é uma ida e volta ao servidor; comportamento rico no cliente significa brigar com o framework ou depender do JavaScript de uma biblioteca de componentes. Testar uma página Faces exige um teste de integração com um navegador ou um contêiner. E o mercado de trabalho de front-end migrou para frameworks TypeScript, então contratar para Faces fica mais difícil a cada ano.
O que justifica mantê-lo (ou, de vez em quando, escolhê-lo): equipes inteiramente Java, aplicações internas em que uma biblioteca de componentes cobre todos os widgets necessários, nenhum contrato de API separado para manter e segurança tratada pelo servidor em vez de por um fluxo de tokens. Se você está construindo um produto novo voltado ao público, use um framework JavaScript consumindo endpoints Jakarta REST. Se você mantém uma aplicação Faces, deixe-a em uma implementação com suporte (Faces 4.x com PrimeFaces 13+), migre para CDI as classes @ManagedBean que restarem e resista à tentação de adicionar JavaScript que contorne a árvore de componentes.
Erros comuns
- Bean
@ViewScopedque não éSerializable. Funciona em um nó e falha comNotSerializableExceptionna replicação de sessão ou na passivação. ImplementeSerializablee marque comotransientos campos não serializáveis. - Fazer trabalho nos getters. Um getter que consulta o banco roda várias vezes por requisição, e de novo a cada chamada Ajax. Carregue em uma ação ou em
@PostConstruct. - Condição
renderedem um bean com escopo de requisição. Durante Apply Request Values o bean é uma instância nova, a condição é false e a ação do botão nunca dispara. Use@ViewScoped. - Componente de comando fora de
<h:form>. Nada é enviado; o estágio Development avisa sobre isso. - Formulários aninhados. HTML inválido; o navegador envia o formulário errado. Um formulário por envio lógico, vários formulários por página.
- Alvo
renderdo Ajax errado. Renderize um contêiner sempre presente, não o próprio componente renderizado condicionalmente. - Guardar o estado da página na sessão. Gera dados desatualizados entre abas e crescimento de memória. Use o escopo de visão para o estado da página e o de sessão apenas para o usuário.
- Esquecer o
faces-redirect=true. A URL fica uma página atrasada e atualizar reenvia o formulário.
Principais pontos
- O Faces mantém uma árvore de componentes no servidor e executa um ciclo de vida de seis fases a cada postback; falhas de conversão ou validação pulam direto para a renderização sem tocar no modelo.
- Páginas Facelets se ligam a beans CDI por meio de expressões EL diferidas
#{…}; o Faces 4.0 removeu o antigo modelo@ManagedBeane as visões JSP. - Use
@ViewScopedpara o estado da página e torne esses beansSerializable; deixe transações e persistência na camada de serviço. - Restrições de Bean Validation nas propriedades do bean são validadas automaticamente, e
<h:messages>torna as falhas visíveis. - Retorne
outcome?faces-redirect=truedas ações para obter o comportamento Post-Redirect-Get; use<f:viewParam>e<f:viewAction>para páginas que podem ir para os favoritos. <f:ajax>executa o mesmo ciclo de vida em uma subárvore e renderiza de novo os componentes indicados; aponte para ids que sempre existem.- O Faces é legado para novos front-ends, mas é mantido ativamente e amplamente implantado: domine o ciclo de vida e você conseguirá dar um bom suporte a ele.
Fontes e leituras complementares
Perguntas frequentes
- O JSF morreu? Vale a pena aprender em 2026?
- Ele é legado para interfaces novas (a maioria dos projetos greenfield usa um framework JavaScript conversando com endpoints REST), mas está longe de estar morto. O Jakarta Faces 4.0 saiu em 2022, PrimeFaces e MyFaces são mantidos ativamente e uma grande base instalada de aplicações de negócio internas roda sobre ele. Se você trabalha com Java corporativo, vai esbarrar nele, e entender o ciclo de vida torna a manutenção bem menos dolorosa.
- O que substituiu o @ManagedBean no Faces 4.0?
- O CDI. Anote a classe com @Named para que ela fique acessível pela EL e dê a ela um escopo CDI: @RequestScoped, @ViewScoped (de jakarta.faces.view), @SessionScoped, @ApplicationScoped, @FlowScoped ou @ConversationScoped. O pacote jakarta.faces.bean e seus escopos foram removidos por completo na 4.0, junto com o suporte a JSP como tecnologia de visão.
- Por que meu botão de comando não chama o método de ação?
- As causas mais comuns, em ordem de frequência: o botão não está dentro de um h:form; um erro de conversão ou validação em algum ponto do formulário interrompeu silenciosamente o ciclo de vida em Process Validations (adicione um componente h:messages para vê-lo); o atributo rendered do botão ou de um elemento pai foi avaliado como false durante Apply Request Values porque sua condição depende de um bean com escopo de requisição; ou formulários aninhados.
- Salvar o estado no servidor ou no cliente?
- No servidor (o padrão), a árvore de componentes fica na sessão e só um token vai para o navegador: é mais seguro e mais leve para a rede, mas consome memória do servidor por visão. No cliente, a árvore é serializada e criptografada em um campo oculto, o que permite escalar horizontalmente sem sticky sessions, ao custo de páginas maiores. A maioria das aplicações mantém o padrão e limita o número de visões por sessão.