Some useful links about Spring Boot 3:

Recently I have migrated:

to Spring Boot 3. Here are some of the problems I have encountered during the migration.

Logback with janino

We had Logback configuration with janino to take advantage of the if statement. I found that using if in the root config element (configuration) does not work. I am not sure why. I just refactored our logging config to not use janino.

<?xml version="1.0" encoding="UTF-8"?>
    <! if can not be used on this level !> 

Java Mail

After replacing all javax dependencies to jakarta first error was:

Caused by: java.lang.IllegalStateException: Not provider of jakarta.mail.util.StreamProvider was found
	at jakarta.mail.util.FactoryFinder.find(
	at jakarta.mail.util.StreamProvider.provider(

Probably one of Spring dependencies (web?) did implement javax.mail. Now I had to add this to pom.xml:


Freemarker - request attributes

We use freemarker to render most of our web pages. Some filters put attributes in the request, later we use those attributes in freemarker templates. In Spring Boot 3 those attributes are not available in templates by default. To expose request attributes in freemarker configuration have to be tweaked like this:

    expose-request-attributes: true

Error pages (freemarker again)

This one was interesting. Request attributes are added to freemarker model also in Spring Soot 3 template model is exposed as request attribute. If there is problem while rendering web page (page from freemarker template) then Spring Boot render error page. In our case error page is also created from freemarker template.

So where is the problem?

Error page inherits all request attributes from original request. Error page rendering goes thur same flow as rendering normal page, expose-request-attributes: true kicks in. AbstractTemplateView tries to put again request attributes into model but model already have all request attributes inside. If duplicate is found then:

class AbstractTemplateView {
        if (model.containsKey(attribute) && !this.allowRequestOverride) {
            throw new ServletException("Cannot expose request attribute '" + attribute +
                    "' because of an existing model object of the same name");

One option is to set allowRequestOverride to true which I preferred not to do, it can be a source of some hard-to-find bugs later. What I found I can do:

    public static class CustomFreemarkerConfig {
        FreeMarkerViewResolver freeMarkerViewResolver(FreeMarkerProperties freeMarkerProperties) {
            FreeMarkerViewResolver resolver = new CustomFreeMarkerViewResolver();
            return resolver;

        private static class CustomFreeMarkerViewResolver extends FreeMarkerViewResolver {
            protected AbstractUrlBasedView instantiateView() {
                return new CustomFreeMarkerView();

            private static class CustomFreeMarkerView extends FreeMarkerView {
                protected void exposeModelAsRequestAttributes(Map<String, Object> model, HttpServletRequest request) {
                    // do nothing

I can do nothing! I decided not to expose the model as a request attribute. I do not use it so that solves my problem.

JOOQ dsl.transaction / @Transactional

Cannot use ContextTransactionalCallable with TransactionProvider of type class org.jooq.impl.DefaultTransactionProvider

We did mix dsl.transaction / @Transactional . Now I migrated all to annotation @Transactional because dsl.transaction was used just a few times.

HttpMethod is not enum

That was pain. In some places, we put HttpMethod from Spring in our model and persisted it to JSON. Now HttpMethod is not enum. Easy to refactor but still 🤦.