Krigl Wurzl
Krigl Wurzl

Reputation: 61

Impossible to start application after upgrade to spring boot 3 and jakarta Faces 4

I' trying to migrate a spring boot application from spring boot 2.7.8 to 3.0.6. The application uses jsf mojarra and primefaces for GUI. The application works fine with spring boot 2.7.8 Spring boot 3 uses spring 6, which needs java 17, and tomcat 10. So I must migrate java EE to jakarta EE 10 and jakarta Faces 4. Unfortunately, the application server doesn't start after migration.
Error : Unable to obtain InjectionProvider from init time FacesContext. Does this container implement the Mojarra Injection SPI? Caused by: java.lang.NullPointerException: Cannot invoke "jakarta.faces.context.FacesContext.getExternalContext()" because "context" is null

full stack trace :

[2023-05-12 13:53:48,821] ERROR jakarta.faces copyInjectionProviderFromFacesContext - Unable to obtain InjectionProvider from init time FacesContext. Does this container implement the Mojarra Injection SPI?
[2023-05-12 13:53:48,824] ERROR jakarta.faces logNoFactory - L’application n’a pas été initialisée correctement au démarrage. Impossible de localiser la Fabrique : jakarta.faces.context.FacesContextFactory. Attempting to find backup.
[2023-05-12 13:53:48,824] ERROR org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/etcsst] log - Servlet.init() for servlet [FacesServlet] threw exception
java.lang.IllegalStateException: Could not find backup for factory jakarta.faces.context.FacesContextFactory. 
    at jakarta.faces.FactoryFinderInstance.notNullFactory(FactoryFinderInstance.java:496) ~[jakarta.jakartaee-api-10.0.0.jar:?]
    at jakarta.faces.FactoryFinderInstance.getFactory(FactoryFinderInstance.java:190) ~[jakarta.jakartaee-api-10.0.0.jar:?]
    at jakarta.faces.FactoryFinder.getFactory(FactoryFinder.java:263) ~[jakarta.jakartaee-api-10.0.0.jar:?]
    at jakarta.faces.webapp.FacesServlet.acquireFacesContextFactory(FacesServlet.java:493) ~[jakarta.jakartaee-api-10.0.0.jar:?]
    at jakarta.faces.webapp.FacesServlet.init(FacesServlet.java:342) ~[jakarta.jakartaee-api-10.0.0.jar:?]
    at org.apache.catalina.core.StandardWrapper.initServlet(StandardWrapper.java:944) ~[tomcat-embed-core-10.1.8.jar:10.1.8]
    at org.apache.catalina.core.StandardWrapper.load(StandardWrapper.java:808) ~[tomcat-embed-core-10.1.8.jar:10.1.8]
    at org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedContext.load(TomcatEmbeddedContext.java:84) ~[spring-boot-3.0.6.jar:3.0.6]
    at java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:183) ~[?:?]
    at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1625) ~[?:?]
    at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:762) ~[?:?]
    at java.util.stream.ReferencePipeline$7$1.accept(ReferencePipeline.java:276) ~[?:?]
    at java.util.TreeMap$ValueSpliterator.forEachRemaining(TreeMap.java:3215) ~[?:?]
    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509) ~[?:?]
    at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) ~[?:?]
    at java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:150) ~[?:?]
    at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:173) ~[?:?]
    at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[?:?]
    at java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:596) ~[?:?]
    at org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedContext.lambda$deferredLoadOnStartup$0(TomcatEmbeddedContext.java:67) ~[spring-boot-3.0.6.jar:3.0.6]
    at org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedContext.doWithThreadContextClassLoader(TomcatEmbeddedContext.java:108) ~[spring-boot-3.0.6.jar:3.0.6]
    at org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedContext.deferredLoadOnStartup(TomcatEmbeddedContext.java:66) ~[spring-boot-3.0.6.jar:3.0.6]
    at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.performDeferredLoadOnStartup(TomcatWebServer.java:305) ~[spring-boot-3.0.6.jar:3.0.6]
    at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.start(TomcatWebServer.java:216) ~[spring-boot-3.0.6.jar:3.0.6]
    at org.springframework.boot.web.servlet.context.WebServerStartStopLifecycle.start(WebServerStartStopLifecycle.java:44) ~[spring-boot-3.0.6.jar:3.0.6]
    at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:178) ~[spring-context-6.0.8.jar:6.0.8]
    at org.springframework.context.support.DefaultLifecycleProcessor$LifecycleGroup.start(DefaultLifecycleProcessor.java:356) ~[spring-context-6.0.8.jar:6.0.8]
    at java.lang.Iterable.forEach(Iterable.java:75) [?:?]
    at org.springframework.context.support.DefaultLifecycleProcessor.startBeans(DefaultLifecycleProcessor.java:155) [spring-context-6.0.8.jar:6.0.8]
    at org.springframework.context.support.DefaultLifecycleProcessor.onRefresh(DefaultLifecycleProcessor.java:123) [spring-context-6.0.8.jar:6.0.8]
    at org.springframework.context.support.AbstractApplicationContext.finishRefresh(AbstractApplicationContext.java:934) [spring-context-6.0.8.jar:6.0.8]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:587) [spring-context-6.0.8.jar:6.0.8]
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) [spring-boot-3.0.6.jar:3.0.6]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:732) [spring-boot-3.0.6.jar:3.0.6]
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:434) [spring-boot-3.0.6.jar:3.0.6]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:310) [spring-boot-3.0.6.jar:3.0.6]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1304) [spring-boot-3.0.6.jar:3.0.6]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1293) [spring-boot-3.0.6.jar:3.0.6]
    at ch.sbb.Application.main(Application.java:24) [classes/:?]

pom-file :

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="maven.apache.org/POM/4.0.0" xmlns:xsi="www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="maven.apache.org/POM/4.0.0 maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
</project>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.0.6</version>
    </parent>
    <groupId>...</groupId>
    <artifactId>...</artifactId>
    <version>3.5.2-SNAPSHOT</version>
    <name>etcsst-cloud</name>
    <packaging>war</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>17</java.version>
        <log4j2.version>2.20.0</log4j2.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-batch</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-ldap</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-oauth2-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring-webmvc</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
        </dependency>
        <!-- Additional JSF libs: Sun's JSF API and Impl. (Mojarra), PrimeFaces and Jasper -->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-1.2-api</artifactId>
            <version>${log4j2.version}</version>
        </dependency>
        <dependency>
            <groupId>jakarta.platform</groupId>
            <artifactId>jakarta.jakartaee-api</artifactId>
            <version>10.0.0</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>jakarta.platform</groupId>
            <artifactId>jakarta.jakartaee-web-api</artifactId>
            <version>10.0.0</version>
            <!-- scope provided should be enough ? -->
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.glassfish</groupId>
            <artifactId>jakarta.faces</artifactId>
            <!--
                probleme : server starts with 3.0.4, but 3.0.4 is not compatible with jakarta.jakartaee-web-api 10, which is needed for tomcat 10
                with version 4.0.1, the server doesn't start, the context not loaded,
                Error : Unable to obtain InjectionProvider from init time FacesContext. Does this container implement the Mojarra Injection SPI?
            -->
            <version>4.0.1</version>
            <!-- <version>3.0.4</version> -->
        </dependency>
        <dependency>
            <groupId>jakarta.servlet.jsp.jstl</groupId>
            <artifactId>jakarta.servlet.jsp.jstl-api</artifactId>
            <version>3.0.0</version>
            <exclusions>
                <exclusion>
                    <groupId>jakarta.servlet</groupId>
                    <artifactId>jakarta.servlet-api</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.primefaces</groupId>
            <artifactId>primefaces</artifactId>
            <version>11.0.0</version>
            <classifier>jakarta</classifier>
        </dependency>
        <dependency>
            <groupId>org.primefaces.extensions</groupId>
            <artifactId>primefaces-extensions</artifactId>
            <classifier>jakarta</classifier>
            <version>11.0.6</version>
        </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.10.1</version>
        </dependency>

        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
        </dependency>

        <!-- file upload -->
        <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>1.5</version>
        </dependency>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.11.0</version>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.12.0</version>
        </dependency>

        <dependency>
            <groupId>joda-time</groupId>
            <artifactId>joda-time</artifactId>
            <version>2.10.10</version>
        </dependency>
        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>2.4</version>
        </dependency>
        <!-- mailing -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-mail</artifactId>
        </dependency>

        <!-- TODO remove after successful migration -->
        <!--spring property management für spring boot upgrade-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-properties-migrator</artifactId>
            <scope>runtime</scope>
        </dependency>
        ...
    </dependencies>
</project>

Jsf Servlet configuration :

import jakarta.faces.webapp.FacesServlet;

import jakarta.servlet.ServletContext;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletPath;
import org.springframework.boot.web.servlet.ServletContextInitializer;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextListener;
import com.sun.faces.vendor.WebContainerInjectionProvider;
import lombok.extern.slf4j.Slf4j;
import ch.sbb.rsw.estamigration.spring.RestoreSpringSessionListener;

@Configuration
@Component
@Slf4j
public class JsfServletConfigurator implements ServletContextInitializer {

    @Value("${javax.faces.PROJECT_STAGE}") // = Development, Production
    private String jsfProjectStage;

    @Value("${server.servlet.context-path}") // /etcsst
    private String dispatcherServletPath; // /etcsst, wird seit spring boot 2.0.9 nötig

    @Override
    public void onStartup(ServletContext servletContext) {
        log.debug("servlet configurator. jsf project stage : '" + jsfProjectStage + "'");

        // kommentare aus xhtml-seiten kommen nicht in output
        servletContext.setInitParameter("jakarta.faces.FACELETS_SKIP_COMMENTS", "true");
        servletContext.setInitParameter("primefaces.THEME", "nova-light");
        servletContext.setInitParameter("com.sun.faces.forceLoadConfiguration", Boolean.TRUE.toString());
        servletContext.setInitParameter("jakarta.faces.PROJECT_STAGE", jsfProjectStage);
// sandbox zone, nothing helps
//        servletContext.setInitParameter("com.sun.faces.config.ConfigManager_INJECTION_PROVIDER_TASK", "com.sun.faces.vendor.WebContainerInjectionProvider");
//        servletContext.setAttribute("com.sun.faces.config.ConfigManager_INJECTION_PROVIDER_TASK", "com.sun.faces.vendor.WebContainerInjectionProvider");
//        servletContext.setAttribute("com.sun.faces.config.ConfigManager_INJECTION_PROVIDER_TASK", new WebContainerInjectionProvider());
//        servletContext.setInitParameter("jakarta.faces.CONFIG_FILES", "/META-INF/faces-config.xml");
//        servletContext.setAttribute("jakarta.faces.CONFIG_FILES", "/META-INF/faces-config.xml");
//        servletContext.setInitParameter("com.sun.faces.injectionProvider", "com.sun.faces.vendor.WebContainerInjectionProvider");
        // source : stackoverflow.com/questions/73112899/spring-boot-3-and-jsf-with-jakarta-not-working
//        servletContext.setAttribute(com.sun.faces.RIConstants.FACES_INITIALIZER_MAPPINGS_ADDED, Boolean.TRUE); // constant fehlt in 4.0.1, comes from 3.0.0
//        servletContext.setAttribute("com.sun.faces.facesInitializerMappingsAdded", Boolean.TRUE);

        // because of Annotation ReinitAfterPassivation. 
        servletContext.addListener(RestoreSpringSessionListener.class);
    }

    @Bean
    public ServletRegistrationBean<FacesServlet> jsfServletRegistrationBean() {
        FacesServlet facesServlet = new FacesServlet();
        ServletRegistrationBean<FacesServlet> servletRegistrationBean = new ServletRegistrationBean<FacesServlet>(facesServlet, "*.xhtml", "/logout",
                // urls for oauth2/SSO
                "/oauth2/authorization/*", "/login/oauth2/code/*");
        servletRegistrationBean.setLoadOnStartup(1);
        servletRegistrationBean.setName("FacesServlet");
        return servletRegistrationBean;
    }

    @Bean
    // returns /etcsst, needed from spring boot 2.0.9
    public DispatcherServletPath jsfServletPathBean() {
        return () -> dispatcherServletPath;
    }

    @Bean
    public RequestContextListener requestContextListener() {
        return new RequestContextListener();
    }
}

faces-config.xml :

<?xml version="1.0" encoding="UTF-8"?>
<faces-config
        xmlns="https://jakarta.ee/xml/ns/jakartaee"
        xmlns:xsi="www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
        https://jakarta.ee/xml/ns/jakartaee/web-facesconfig_4_0.xsd"
        version="4.0">
 
    <application>
        <!-- 
            RswSpringBeanFacesELResolver is an extension of SpringBeanFacesELResolver. Checks user roles, reads mask labels from database
        -->
        <el-resolver>
            ch.sbb.rsw.etcsst.web.util.RswSpringBeanFacesELResolver
        </el-resolver>
     
        <locale-config>
            <default-locale>de</default-locale>
            <supported-locale>fr</supported-locale>
            <supported-locale>it</supported-locale>
            <supported-locale>en</supported-locale>
        </locale-config>
        <message-bundle>EtcsstResources</message-bundle>
        <resource-bundle>
            <base-name>EtcsstResources</base-name>
            <var>Msgs</var>
        </resource-bundle>
    </application>

    <lifecycle>
        <phase-listener>ch.sbb.rsw.estamigration.jsf.MultipleFormSubmitPreventionPhaseListener</phase-listener>
    </lifecycle>

    <factory>
        <!-- Doesn't help. The RswExceptionHandlerFactory was already defined here and worked before upgrade. 
        After upgrade, a display in constructor doesn't longer appear in log, so the class is no longer loaded after upgrade 
        defining FacesContextFactoryImpl here has no effect
        -->
        <faces-context-factory>com.sun.faces.context.FacesContextFactoryImpl</faces-context-factory>
        <exception-handler-factory>
            ch.sbb.rsw.estamigration.exception.RswExceptionHandlerFactory
        </exception-handler-factory>
    </factory>

    <!-- NAVIGATION RULES -->
    <navigation-rule>
        <from-view-id>/*</from-view-id>
        <navigation-case>
            <from-outcome>home</from-outcome>
            <to-view-id>/pages/index.xhtml</to-view-id>
        </navigation-case>
        <navigation-case>
            <from-outcome>login</from-outcome>
            <to-view-id>/login.xhtml</to-view-id>
        </navigation-case>
    </navigation-rule>

    <converter>
        <converter-for-class>ch.sbb.rsw.etcsst.model.Aktor</converter-for-class>
        <converter-class>ch.sbb.rsw.etcsst.web.converter.AktorConverter</converter-class>
    </converter>
    ... here more converters
        
</faces-config>

If I use instead of org.glassfish:jakarta.faces:4.0.1 the version 3.0.4, the server starts fine, but as 3.0.4 is not compatible with jakarta EE 10, it fails on the first attempt to display a xhtml-page, 3.0.4 trying to call classes/methods of jakarta EE 9 missing in jakarta EE 10 Debugging FactoryFinderInstance, the faces context is missing (but exists with 3.0.4)

I tried to define the Factory classes as services in META-INF/services, example, file named jakarta.faces.context.FacesContextFactory with content org.primefaces.context.PrimeFacesContextFactory the defined class will be loaded, but the error Unable to obtain InjectionProvider from init time FacesContext. Does this container implement the Mojarra Injection SPI? appear for each so injected class, the server still doesn't start

I tried to define the Factory classes in faces-config.xml too, see whole file below, it hasn's any effect

    <factory>
        <!-- Doesn't help. The RswExceptionHandlerFactory was already defined here and worked before upgrade. 
        After upgrade, a display in constructor doesn't longer appear in log, so the class is no longer loaded after upgrade 
        defining FacesContextFactoryImpl here has no effect
        -->

        <faces-context-factory>com.sun.faces.context.FacesContextFactoryImpl</faces-context-factory>
        <exception-handler-factory>
            ch.sbb.rsw.estamigration.exception.RswExceptionHandlerFactory
        </exception-handler-factory>
    </factory>

I tried to define a configuration class for Faces config :

import org.springframework.context.annotation.Configuration;
import jakarta.faces.annotation.FacesConfig;
import lombok.extern.slf4j.Slf4j;

// this class should be able to replace the file faces-config.xml, which won't be loaded/understood/processed with jakarta-faces 4
// source hantsy.gitbook.io/java-ee-8-by-example/jsf/jsf-activation
// without @Configuration annotation, the class won't be instanciated
@Configuration
@FacesConfig
@Slf4j
public class Grmbl {
    public Grmbl(){
        log.error("!!! INSTANTIATION DE @FacesConfig par Grmbll");
    }
}

I looked for a solution on the net, tried about all possible hints, nothing helps, I'm stuck on the same error message

Any help, any hint will be greatly appreciated.

Upvotes: 6

Views: 3703

Answers (1)

Krigl Wurzl
Krigl Wurzl

Reputation: 61

I replaced mojarra by myFaces. I could solve the problem using a hello world-example basing on this post : https://prog.world/jakarta-faces-and-spring-boot/

Upvotes: 0

Related Questions