javalin / javalin-rendering

Artifact/module for server-side template/markdown rendering in Javalin: https://javalin.io/plugins/rendering
Apache License 2.0
10 stars 7 forks source link

Using Thymeleaf with Javalin 5.0.1 #11

Closed northcoder-repo closed 2 years ago

northcoder-repo commented 2 years ago

I am testing a Javalin 5.0.1 "Hello world" app, using Thymeleaf 3.0.15 (latest release).

Javalin starts OK, but when I try to render my Thymeleaf template, I get a stack trace containing:

Caused by: java.lang.ClassNotFoundException: org.thymeleaf.web.servlet.JakartaServletWebApplication

The application:

import io.javalin.Javalin;
import io.javalin.rendering.template.JavalinThymeleaf;
import org.thymeleaf.TemplateEngine;

public class App {

    public static void main(String[] args) {
        Javalin.create(config -> {
            config.showJavalinBanner = false;
            JavalinThymeleaf.init(new TemplateEngine());
        })
                .get("/test", ctx -> ctx.render("test.html"))
                .start(7070);
    }

}

The HTML template (which happens to not have any Thymeleaf variables in it, but that does not appear to be relevant to the issue):

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <title>Javalin 5 Thymeleaf Test</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
    </head>

    <body>
        <div>Hello world!</div>
    </body>
</html>

The Maven dependencies:

    <dependencies>
        <dependency>
            <groupId>io.javalin</groupId>
            <artifactId>javalin</artifactId>
            <version>5.0.1</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>1.7.36</version>
        </dependency>
        <dependency>
            <groupId>io.javalin</groupId>
            <artifactId>javalin-rendering</artifactId>
            <version>5.0.1</version>
        </dependency>
        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf</artifactId>
            <version>3.0.15.RELEASE</version>
        </dependency>
    </dependencies>

Steps to reproduce the problem:

1) Start the app (it starts cleanly):

[main] INFO io.javalin.Javalin - Starting Javalin ...
[main] INFO org.eclipse.jetty.server.Server - jetty-11.0.12; built: 2022-09-14T02:38:00.723Z; git: d5b8c29485f5f56a14be5f20c2ccce81b93c5555; jvm 17.0.4+8
[main] INFO org.eclipse.jetty.server.session.DefaultSessionIdManager - Session workerName=node0
[main] INFO org.eclipse.jetty.server.handler.ContextHandler - Started i.j.j.@43bc63a3{/,null,AVAILABLE}
[main] INFO org.eclipse.jetty.server.AbstractConnector - Started ServerConnector@b59d31{HTTP/1.1, (http/1.1)}{0.0.0.0:7070}
[main] INFO org.eclipse.jetty.server.Server - Started Server@4f9a3314{STARTING}[11.0.12,sto=0] @573ms
[main] INFO io.javalin.Javalin - Listening on http://localhost:7070/
[main] INFO io.javalin.Javalin - You are running Javalin 5.0.1 (released October 2, 2022).
[main] INFO io.javalin.Javalin - Javalin started in 357ms \o/

2) In a browser, try to open http://localhost:7070/test.

This generates the following stack trace:

[JettyServerThreadPool-39] ERROR io.javalin.Javalin - Exception occurred while servicing http-request
java.lang.NoClassDefFoundError: org/thymeleaf/web/servlet/JakartaServletWebApplication
    at io.javalin.rendering.template.JavalinThymeleaf.render(JavalinThymeleaf.kt:25)
    at io.javalin.rendering.JavalinRenderer.renderBasedOnExtension(JavalinRenderer.kt:21)
    at io.javalin.http.Context.render(Context.kt:433)
    at io.javalin.http.Context.render(Context.kt:436)
    at org.northcoder.javalin5.App.lambda$main$1(App.java:19)
    at io.javalin.routing.HandlerEntry.handle(HandlerEntry.kt:19)
    at io.javalin.http.servlet.DefaultTasks.HTTP$lambda-8$lambda-6$lambda-5(DefaultTasks.kt:35)
    at io.javalin.http.servlet.JavalinServlet.handleTask(JavalinServlet.kt:79)
    at io.javalin.http.servlet.JavalinServlet.handleSync(JavalinServlet.kt:46)
    at io.javalin.http.servlet.JavalinServlet.service(JavalinServlet.kt:34)
    at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:587)
    at io.javalin.jetty.JavalinJettyServlet.service(JavalinJettyServlet.kt:58)
    at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:587)
    at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:764)
    at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:529)
    at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:221)
    at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:1571)
    at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:221)
    at io.javalin.jetty.JettyServer$start$wsAndHttpHandler$1.doHandle(JettyServer.kt:57)
    at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:176)
    at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:484)
    at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:1544)
    at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:174)
    at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1302)
    at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:129)
    at org.eclipse.jetty.server.handler.StatisticsHandler.handle(StatisticsHandler.java:173)
    at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:122)
    at org.eclipse.jetty.server.Server.handle(Server.java:563)
    at org.eclipse.jetty.server.HttpChannel.lambda$handle$0(HttpChannel.java:505)
    at org.eclipse.jetty.server.HttpChannel.dispatch(HttpChannel.java:762)
    at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:497)
    at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:282)
    at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:314)
    at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:100)
    at org.eclipse.jetty.io.SelectableChannelEndPoint$1.run(SelectableChannelEndPoint.java:53)
    at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:933)
    at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1077)
    at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: java.lang.ClassNotFoundException: org.thymeleaf.web.servlet.JakartaServletWebApplication
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
    at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520)
    ... 38 more

The migration guide mentions:

To use template engines in Javalin 5 you have to add this dependency, and call MyTemplateEngine.init(), which will make the template engine register itself on JavalinRenderer with the proper extension. You could also call JavalinRender.register(engine, extension...) manually.

My attempt above uses the first init() approach - unless I am doing it wrong.

I also tried the second approach using JavalinRender.register(...):

import io.javalin.Javalin;
import io.javalin.rendering.template.JavalinThymeleaf;
import io.javalin.rendering.JavalinRenderer;
import org.thymeleaf.TemplateEngine;

public class App {

    public static void main(String[] args) {
        Javalin.create(config -> {
            config.showJavalinBanner = false;
            JavalinRenderer.register(new JavalinThymeleaf(new TemplateEngine()), 
                    ".html");
        })
                .get("/test", ctx -> ctx.render("test.html"))
                .start(7070);
    }

}

This led to the same stack trace.


Am I trying to initialize Thymeleaf incorrectly?

I looked at the Javalin Thymeleaf tests, but I did not understand how these relate to the migration guide.

tipsy commented 2 years ago

@northcoder-repo I just checked our POM, and there is a comment in the Thymeleaf dependency:

<dependency>
    <groupId>org.thymeleaf</groupId>
    <artifactId>thymeleaf</artifactId>
    <!-- 3.1.0 is required for jakarta support -->
    <!-- Ref: https://github.com/thymeleaf/thymeleaf/issues/811 -->
    <version>3.1.0.M1</version>
    <optional>true</optional>
</dependency>

Javalin 5 moved to Jetty 11 (which moved to Jakarta, the new servlet spec). So, you will need to use a version of Thymeleaf that supports Jakarta.

northcoder-repo commented 2 years ago

Understood - thank you.

This problem is resolved by using Thymeleaf 3.1.