Search code examples
springservletsgrailstomcat7

Grails application response.outputStream << fails since upgrade to Grails 3.3


I have a Grails application that I have recently upgraded to 3.3 from 2.5. Generally things are working but today we ran across a problem that seems to be shared by others but I cannot find a solution.

In a controller I have a method that appends a string to the response.outputStream.

The code now appears as

    response.status = OK.value()
    response.contentType = 'text/csv;charset=UTF-8'
    response.setHeader "Content-disposition", "attachment; filename=rcCandidate.csv"
    response.outputStream << converted
    response.outputStream.flush()
    response.outputStream.close()

based on a suggestion found here

http://sergiodelamo.es/grails-tips-how-to-output-csv-from-a-grails-3-controller/

This code executes just fine on my test environment

$ grails -version
| Grails Version: 3.3.5
| Groovy Version: 2.4.15
| JVM Version: 1.8.0_162

but fails badly on the production server

$ java -version
java version "1.8.0_161"
Java(TM) SE Runtime Environment (build 1.8.0_161-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.161-b12, mixed mode)

$ apt list | grep tomcat
tomcat7/trusty-security,trusty-updates,now 7.0.52-1ubuntu0.13 all [installed]

The failures are reported starting with:

org.springframework.web.util.NestedServletException: Handler dispatch failed; nested exception is java.lang.NoClassDefFoundError: javax/servlet/WriteListener

followed by a stack trace, then this

Caused by: java.lang.NoClassDefFoundError: javax/servlet/WriteListener

then more stack trace and similar messages about WriteListener

I have seen suggestions to replace this line

provided "org.springframework.boot:spring-boot-starter-tomcat"

with

compile "org.springframework.boot:spring-boot-starter-tomcat"

But as pointed out here

https://docs.grails.org/latest/guide/deployment.html

that is not a good idea, and indeed when I tried it, tomcat did not start up.

I believe I've read somewhere that I might be able to cure this problem by replacing Tomcat7 with Tomcat8; however right now I'm running Ubuntu 14.04 on the server and Tomcat8 is not on offer in the repositories, so it's not quite straightforward to test that.

Does anyone have any suggestions for me? Thanks in advance.


Solution

  • You can fix this by adding @CompileStatic to your method, but that is not always feasible. We have fixed this problem in our applications by adding a static utility method:

    @CompileStatic
    public static sendResponseData(ServletOutputStream outputStream, String s) { // but this could be byte[] s or InputStream s or whatever you need
        outputStream << s
    }
    

    and then calling that instead of the left shift operation.

    You may need to add additional method signatures so that they can be statically compiled but the concept is the same. If I recall correctly, the left shift operator here uses some annotation or something (clearly I don't remember details!) that is not included by default (on Tomcat 7) but is also not needed.

    Note that we also added

    @CompileStatic
    public static flushOutputStream(ServletOutputStream outputStream) {
        outputStream.flush()
    }
    

    for convenience since that one has to be statically compiled as well.