Hello,

Maybe I miss something on the following, but below are my thoughts about
a/the potential issue.

Tracking where and how tempdir can become null I noticed the following:
StandardContext.postWorkDirectory() sets TEMPDIR and makes the attribute
read-only so ApplicationContext can't remove it.

Only way for tempdir to be null is an ApplicationContext without TEMPDIR
set, thus without postWorkDirectory() being run.
This can be achieved by running getServletContext() outside of
startInternal() if context == null.

Then in stopInternal() I spotted the following:
// Reset application context
context = null;

Chris since you mentioned reloading, StandardContext.reload() does stop(),
sets context = null and then start(). getServletContext() is not a
synchronized method and context field is not volatile. If any thread calls
getServletContext(), during context being null before start(), it creates
an ApplicationContext without TEMPDIR set. And  although there is a comment
stating "This method is called multiple times during context start which is
single threaded so there is no concurrency issue", getServletContext() is
often called for various Tomcat events e.g fireRequestDestroyEvent.

I have made a fix for this and included a test to verify the behavior in
the following PR: https://github.com/apache/tomcat/pull/994
I would appreciate any kind of review.

Kind regards,
Dimitris


On Tue, Apr 21, 2026 at 7:41 PM Christopher Schultz <
[email protected]> wrote:

> Konstantin,
>
> On 4/20/26 3:08 PM, Konstantin Kolinko wrote:
> > пн, 20 апр. 2026 г. в 20:12, Christopher Schultz <
> [email protected]>:
> >>
> >> All,
> >>
> >> I just observed something in production that caused all kinds of
> weirdness.
> >>
> >> java.lang.NullPointerException: Cannot invoke
> >> &quot;java.io.File.toURI()&quot; because &quot;base&quot; is null
> >>
> org.apache.jasper.JspCompilationContext.createOutputDir(JspCompilationContext.java:676)
> >>      at
> >>
> org.apache.jasper.JspCompilationContext.getOutputDir(JspCompilationContext.java:196)
> >>      at
> >>
> org.apache.jasper.JspCompilationContext.getClassFileName(JspCompilationContext.java:530)
> >>      at
> org.apache.jasper.compiler.Compiler.isOutDated(Compiler.java:441)
> >>      at
> org.apache.jasper.compiler.Compiler.isOutDated(Compiler.java:412)
> >>      at
> >>
> org.apache.jasper.JspCompilationContext.compile(JspCompilationContext.java:584)
> >>      at
> >>
> org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:379)
> >>      at
> >> org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:356)
> >>      at
> org.apache.jasper.servlet.JspServlet.service(JspServlet.java:307)
> >>      at javax.servlet.http.HttpServlet.service(HttpServlet.java:623)
> >>
> >>
> >> It looks like the value of javax.servlet.context.tempdir became null at
> >> some point.
> >
> > Hi, Chris!
> >
> > Looking at the code (e.g. in Tomcat 11),
> > 1) the "base" in org.apache.jasper.JspCompilationContext.createOutputDir
> > is "File base = options.getScratchDir();"
> >
> > 2) the "options" in org.apache.jasper.servlet.JspServlet.init() are
> >              // Use the default Options implementation
> >              options = new EmbeddedServletOptions(config, context);
> > (Those can be reconfigured to use a custom class, but it is unlikely
> > that you use that.)
>
> I have nothing custom; I'm using a standard Tomcat out of the box. Only
> standard changes to server.xml for <Connector>s.
>
> > 3) org.apache.jasper.EmbeddedServletOptions constructor validates the
> value
> > and logs with "fatal" severity if the value is null or not an existing
> > directory:
> >
> >          if (scratchDir == null) {
> >              log.fatal(Localizer.getMessage("jsp.error.no
> .scratch.dir"));
> >              return;
> >          }
> >
> >          if (!(scratchDir.exists() && scratchDir.canRead() &&
> > scratchDir.canWrite() && scratchDir.isDirectory())) {
> >              log.fatal(Localizer.getMessage("jsp.error.bad.scratch.dir",
> > scratchDir.getAbsolutePath()));
> >          }
> >
> > BTW, seeing a "return" above is odd, as this is in a constructor and
> > thus it skips processing of all other configuration options.
> >
> > The scratchDir field in EmbeddedServletOptions is final, so there is
> > no way to change it afterwards, so you should have seen the log
> > message from EmbeddedServletOptions constructor.
> >
> > 4) Looking at o.a.c.core.StandardContext.postWorkDirectory()
> >
> >          if (!dir.mkdirs() && !dir.isDirectory()) {
> >              log.warn(sm.getString("standardContext.workCreateFail",
> > dir, getName()));
> >          }
> >          ...
> >          context.setAttribute(ServletContext.TEMPDIR, dir);
> >          context.setAttributeReadOnly(ServletContext.TEMPDIR);
> >
> > The "dir" in a "dir.mkdirs()" call cannot be null, and the last line
> > prevents further changes to the value.
> > Thus my question about a stray "return" in point 3) becomes moot, as
> > it should not happen.
> >
> > Yet you saw something.
>
> Yup.
>
> > May it be that you are using some framework that mocks, wraps,
> > "instruments" the mentioned classes?
> > All is odd, given that the srratchDir field in EmbeddedServletOptions is
> final.
>
> I'm not using JSPs much at all. I think one thing that may explain
> what's happening specifically above is that we saw a file-upload
> component failing due to a null temp dir, and only then did I launch a
> JSP to inspect the servlet context. (We have a very small number of JSPs
> deployed, so it's not entirely surprising that the JSP engine might not
> be loaded at all until I tried this test, after knowing the temp dir was
> null).
>
> > (A weirdness similar to an issue with JavaMelody, whatever that tool
> > is. E.g. oct 2025:
> > https://lists.apache.org/thread/v40g4opbcgs2s5ncdsl5qwg1l720oyfx
> >
> > What is your version of Tomcat?
>
> 9.0.108
>
> > Any other oddities in the logs?
>
> Nothing. I looked for a failure to discover the temp and/or work
> directories, and I see nothing in catalina.out. (The log file goes back
> months, and our most recent Tomcat restart was in March, while we do
> reload the application periodically if we deploy a hot fix without
> restarting the container).
>
> The work directory is there, has the correct ownership, etc. and
> contains files from both before and after the mishap.
>
> > Were there context starts/stops/restarts at the same time?
>
> Not that we know of. Certainly not after we started investigating. This
> was not a single error thrown a single time, either. Once the problem
> was identified, it was trivially reproducible by loading any JSP.
>
> Our fix was to restart the container and everything looks good, now.
>
> So the root cause was that the temp dir servlet context attribute became
> null, but I have no idea how that happened.
>
> Unfortunately, I had to restart the container so I can't do any more
> investigation at this point. :/
>
> -chris
>
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: [email protected]
> For additional commands, e-mail: [email protected]
>
>

Reply via email to