Hot questions for Using GlassFish in spring boot

Question:

I have a vanilla spring boot app that consists of the following pom.xml

 <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>xxx.alexius</groupId>
    <artifactId>myapp</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <name>my app</name>
<description>Core Application Platform</description>
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.2.2.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <java.version>1.7</java.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-mongodb</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-mobile</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-social-facebook</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-social-twitter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.postgresql</groupId>
        <artifactId>postgresql</artifactId>
        <version>9.4-1201-jdbc41</version>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>2.3.2</version>
            <configuration>
                <source>1.7</source>
                <target>1.7</target>
                <compilerArgument>-Xlint:all</compilerArgument>
                <showWarnings>true</showWarnings>
                <showDeprecation>true</showDeprecation>
            </configuration>
        </plugin>
    </plugins>
</build>

When I deploy this to a Tomcat everything works fine but when I try to deploy this on a Glassfish 4.1 server I get the following error

SEVERE:   Class [ liquibase/integration/spring/SpringLiquibase ] not found. Error while loading [ class org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration$LiquibaseConfiguration ]
SEVERE:   Exception while deploying the app [platform]
SEVERE:   Exception during lifecycle processing
java.lang.ArrayStoreException: sun.reflect.annotation.TypeNotPresentExceptionProxy

Any ideas on what is going wrong? Is it a Spring or Glassfish bug?


Answer:

It looks like this a bug in Glassfish: GLASSFISH-21265

You can try to get around that by adding metadata-complete="true" in your web.xml like this:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.1" 
         metadata-complete="true"
         xmlns="http://xmlns.jcp.org/xml/ns/javaee" 
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd">
</web-app>

The metadata-complete="true" indicates that the JAR files in /WEB-INF/lib doesn't need to be scanned for Servlet 3.0 specific annotations, but the webapp's own classes will still be scanned.

See also:

Question:

I am trying to deploy my jHipster app war on to Glassfish and keep getting the following error....

"The lifecycle method [initApplication] must not throw a checked exception. Related annotation information: annotation [@javax.annotation.PostConstruct()] on annotated element [public void com.org.myapp.Application.initApplication() throws java.io.IOException] of type [METHOD]."

Reading over some posts, it looks like a glassfish issue. I also tried the suggestions from the post 'https://github.com/spring-projects/spring-boot/issues/1355' by 'dsyer'. It did not work. I am still having the issue.

Has anyone encountered this issue? How did you get over it? Really appreciate any help!


Answer:

The error message tells you that you have annotated your initApplication() method with @PostConstruct which has a throws-declaration, which is not allowed. Remove the throws IOException from its signature, catch the IOException, rethrow a RuntimeException, and the error should disappear.

Question:

I have a problem with Spring-Boot application that I want to deploy to Payara 5. I have visited Spring Initializr page, I've filled group, artifact, and added Web dependency. To make it possible to deploy application to Payara, I've removed dependencies to Tomcat, I've adjusted @SpringBootApplication annotated class, to extend SpringBootServletInitializer. And I've created very simple RestController that returns very simple Pojo.

Here is the code: pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.1.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.sample</groupId>
<artifactId>rest-payara</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>rest-glassfish</name>
<description>Demo project for Spring Boot</description>
<packaging>war</packaging>

<properties>
    <java.version>1.8</java.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>2.1.1.RELEASE</version>
        <exclusions>
            <exclusion>
                <artifactId>spring-boot-starter-tomcat</artifactId>
                <groupId>org.springframework.boot</groupId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <artifactId>spring-boot-starter-tomcat</artifactId>
        <groupId>org.springframework.boot</groupId>
        <version>2.1.1.RELEASE</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <version>2.1.1.RELEASE</version>
        <scope>test</scope>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>
</project>

Application class:

package com.sample.restpayara;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;

@SpringBootApplication
public class RestPayaraApplication extends SpringBootServletInitializer {

    public static void main(String[] args) {
        SpringApplication.run(RestPayaraApplication.class, args);
    }

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(RestPayaraApplication.class);
    }
}

Rest controller:

package com.sample.restpayara;

import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class RestApiController {

  @GetMapping(value = "/sample-pojo", produces = { MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE })
  public SamplePojo getSamplePojo() {
    return new SamplePojo("Sample pojo");
  }
}

Pojo:

package com.sample.restpayara;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name = "PojoRoot")
public class SamplePojo {

  @XmlElement(name = "pojoContent")
  private String content;

  public SamplePojo() {
  }

  public SamplePojo(String content) {
    this.content = content;
  }

  public String getContent() {
    return content;
  }
}

When I run this application with

mvn spring-boot:run

Everything works like I want, CURL request:

curl -k -i -X GET "http://localhost:8080/sample-pojo" -H "accept: application/xml" -H "Content-Type: application/xml"

Returns:

HTTP/1.1 200 
Content-Type: application/xml
Transfer-Encoding: chunked
Date: Fri, 28 Dec 2018 09:26:08 GMT

<?xml version="1.0" encoding="UTF-8" standalone="yes"?><PojoRoot><pojoContent>Sample pojo</pojoContent></PojoRoot>

However when I deploy this code to Payara and I do CURL request:

curl -k -i -X GET "https://my-payara-domain.local:8181/rest-payara-0.0.1-SNAPSHOT/sample-pojo" -H "accept: application/xml" -H "Content-Type: application/xml"

i receive response:

HTTP/2 200 
content-type: application/xml;charset=UTF-8

<SamplePojo><content>Sample pojo</content></SamplePojo>

And here is the problem - why are JAXB annotations ignored on Payara and what do I have to do to make them work?


Answer:

For anyone ever fighting with similar problem - the root cause of the issue was related to the fact, that MappingJackson2HttpMessageConverter was kicking in on Payara 5, while Jaxb2RootElementHttpMessageConverter was not there. I found a solution for my problem by providing a configuration:

package com.sample.restpayara;

import java.util.List;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

@Configuration
public class JaxbSupportConfiguration extends WebMvcConfigurationSupport {

  @Override
  public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    converters.add(new Jaxb2RootElementHttpMessageConverter());
    converters.add(new MappingJackson2HttpMessageConverter());
  }
}

Hope it helps someone in the future, for me it took 1,5 day to figure it out ;(

=================================

UPDATE: First solution did turn on Web MVC and caused static files not to be served anymore. I've managed to find a final solution by providing configuration:

package com.sample.restpayara;

import java.util.Arrays;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter;

@SpringBootApplication
public class RestPayaraApplication extends SpringBootServletInitializer {

    public static void main(String[] args) {
        SpringApplication.run(RestPayaraApplication.class, args);
    }

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(RestPayaraApplication.class);
    }

    @Bean
    public HttpMessageConverters converters() {
        return new HttpMessageConverters(true, Arrays.asList(
                new MappingJackson2HttpMessageConverter(),
                new Jaxb2RootElementHttpMessageConverter())
        );
    }
}

Question:

I have a glassfish application which creates its DB schema with liquibase. I have migrated the same application to Spring Boot. I did not drop the DB schema. When I deploy the Spring application and the liquibase scripts run, I get

java.sql.SQLSyntaxErrorException: ORA-00955: name is already used by an existing object

when executing the changeset for creating one of the tables.

I need to specify there is no change in the liquibase scripts and the database changelog lock is acquired successfully.

Shouldn't it skip all the table creation steps? I plug in the same application to the same DB. Have you encountered this situation before?

UPDATE: is it possible that this might be related to the MD5 sum stored in the changelog file ? So the md5 computed by the new application doesn't match the one computed by the old one and the scripts are triggered, causing the obvious exception ?

Many thanks


Answer:

I don't think you have a checksum difference - that would cause a different error message. What I think is likely is that the DATABASECHANGELOG table has a different changelog path for the changes than what is being reported by Liquibase.

Changesets are identified by 3 things - the changeset id, the author, and the path. When Liquibase is deciding whether a changeset from a changelog should be deployed to a particular database, it looks at the DATABASECHANGELOG table and retrieves that information, compares it with the information in the changelog file, and doeesn't try to deploy anything that matches up. In this case, I think it detects differences in the path and tries to re-deploy the change.