cloudSdkVersion after 544.0.0 broke testing java web application with security-constraint

I have an application I’ve run on app-engine for years, starting with gen1 and migrated to gen2 back in early 2025. It’s a simple old-style servlet/JSP app - nothing particularly special, but good enough for what I need to do. The last time I updated it in 25Q3 was just before the adoption of Java25 and Jakarta EE11. I always verify by running locally with appengine:run before completing the appengine:deploy.

I’ve spent the last few days fighting failures in the appengine:run local testing related to my use of to protect certain pages from anonymous access. Attempts to access the pages behind the started throwing ClassNotFound exceptions (abbreviated stack trace):

java.lang.NoClassDefFoundError: org/eclipse/jetty/ee8/security/Authenticator
	at com.google.apphosting.runtime.jetty.AppEngineAuthentication$AppEngineUserIdentity.isUserInRole(AppEngineAuthentication.java:382)
	at org.eclipse.jetty.security.SecurityHandler.isAuthorized(SecurityHandler.java:690)

<<<remainder of the stack trace removed since it's not immediately relevant>>>

After doing a bit of research, I was able to get around the issue by forcing Java21, Jakarta EE10, and cloudSdkVersion 544.0.0. From what I can tell, the most recent cloudSdkVersions are missing some bits, or not configured properly, and I was unable to resolve the problem without forcing the cloudSdkVersion in the appengine-maven-plugin:

      <plugin>
        <groupId>com.google.cloud.tools</groupId>
        <artifactId>appengine-maven-plugin</artifactId>
        <version>2.8.6</version>
        <configuration>
          <projectId>my-site</projectId>
          <version>GCLOUD_CONFIG</version>
          <cloudSdkVersion>544.0.0</cloudSdkVersion
        </configuration>
      </plugin>

Is there a… more appropriate fix for this problem? Is there an open bug to fix this issue? I looked at trying to include the missing bits, but it all seems to get overridden by the cloudSdk package, plus I don’t want to force old support when deploying, just while testing.

Thanks for the report, looking at the issue now. Glad you have a temporary fix.

For faster visibility, you can also file bugs on GitHub · Where software is built

Cheers,

Ludo

It seems that appengine-java-sdk/jetty121/jetty-home/lib/jetty-security-12.1.5.jar is in the devappserver impl classloader and maybe should be in the shared classloader with the app.

Hum, cannot repro with latest Cloud CLI and a java25 EE8 test app with an /admin endpoint and this in web.xml


<security-constraint>
        <web-resource-collection>
            <web-resource-name>Admin Area</web-resource-name>
            <url-pattern>/admin/*</url-pattern>
            <http-method>GET</http-method>
            <http-method>POST</http-method>
        </web-resource-collection>
        
        <auth-constraint>
            <role-name>admin</role-name>
        </auth-constraint>

        <user-data-constraint>
            <transport-guarantee>CONFIDENTIAL</transport-guarantee>
        </user-data-constraint>
    </security-constraint>

    <security-role>
        <description>The role required to access administrative pages</description>
        <role-name>admin</role-name>
    </security-role>

    <login-config>
        <auth-method>BASIC</auth-method>
        <realm-name>AppEngine</realm-name>
    </login-config>
```

Can you share more details on a repro case, ie your web.xml and appengine-web.xml (or relevant fragments)

Also you mention EE11 but the stack trace is for EE8, so appengine-web.xml would help.

I’ll upload as soon as I can… currently have limited internet access.

OK… so let’s see if I can help a little.

First, re: the observation on EE8 vs EE11 in the stack trace. Here is a little more of the stack trace in which you’ll see that the EE11 SessionHandler makes the call to IsAuthorized which in turn hits Jetty… and then the EE8 Authenticator is throwing the NoClassDefFoundError:

java.lang.NoClassDefFoundError: org/eclipse/jetty/ee8/security/Authenticator
	at com.google.apphosting.runtime.jetty.AppEngineAuthentication$AppEngineUserIdentity.isUserInRole(AppEngineAuthentication.java:382)
	at org.eclipse.jetty.security.SecurityHandler.isAuthorized(SecurityHandler.java:690)
	at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:527)
	at org.eclipse.jetty.ee11.servlet.SessionHandler.handle(SessionHandler.java:763)

So it looks like EE11 is using some EE8 bits… which seems consistent with other things I’ve read about EE11.

Now, let’s look at two variants of the appengine-web.xml, web.xml and pom.xml.

Firstly, let’s take a look at what produced this stack trace.

Both variants use the same body in the web.xml

  <servlet>
    <servlet-name>secure</servlet-name>
    <jsp-file>/secure/secure.jsp</jsp-file>
  </servlet>
  
  <servlet-mapping>
    <servlet-name>secure</servlet-name>
    <url-pattern>/secure</url-pattern>
  </servlet-mapping>

  <servlet>
    <servlet-name>secret</servlet-name>
    <jsp-file>/secure/secret.jsp</jsp-file>
  </servlet>
  
  <servlet-mapping>
    <servlet-name>secret</servlet-name>
    <url-pattern>/secret</url-pattern>
  </servlet-mapping>

  <!-- Secure the non-public content -->
  <security-constraint>
    <web-resource-collection>
      <web-resource-name>secure</web-resource-name>
      <url-pattern>/secure/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
      <role-name>admin</role-name>
    </auth-constraint>
  </security-constraint>

  <!-- Secure the sensitive content -->
  <security-constraint>
    <web-resource-collection>
      <web-resource-name>secret</web-resource-name>
      <url-pattern>/secret</url-pattern>
    </web-resource-collection>
    <auth-constraint>
      <role-name>admin</role-name>
    </auth-constraint>
    <user-data-constraint>
      <transport-guarantee>CONFIDENTIAL</transport-guarantee>
    </user-data-constraint>
  </security-constraint>

The difference between the two is that for java25, the jakartaee schema changes to 6.1

<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_1.xsd"
    version="6.1">

The appengine-web.xml built for the latest release of AppEngine targets java25 is Plain Jane simple:

<?xml version="1.0" encoding="utf-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
  <runtime>java25</runtime>
  <app-engine-apis>true</app-engine-apis>
</appengine-web-app>

And in the pom.xml, the jarkarta-servlet-api is 6.1.0

...
    <dependency>
      <groupId>jakarta.servlet</groupId>
      <artifactId>jakarta.servlet-api</artifactId>
      <version>6.1.0</version>
      <type>jar</type>
      <scope>provided</scope>
    </dependency>
...
  <build>
    ...
      <plugin>
        <groupId>com.google.cloud.tools</groupId>
        <artifactId>appengine-maven-plugin</artifactId>
        <version>2.8.6</version>
        <configuration>
          <projectId>test-site</projectId>
          <version>GCLOUD_CONFIG</version>
        </configuration>
      </plugin>
    </plugins>
  </build>

Now, in order to test using cloudSdkVersion 544.0.0 I switched everything to target java21, EE10 and the pinned cloudSdkVersion. So, starting the jakartaee schema back to 6.0

<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
    version="6.0">

Then appengine-web.xml forces the use of EE10 with java21

<?xml version="1.0" encoding="utf-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
  <runtime>java21</runtime>
  <system-properties>
    <property name="appengine.use.EE10" value="true"/>
  </system-properties>
  <app-engine-apis>true</app-engine-apis>
</appengine-web-app>

And in the pom.xml, the jarkarta-servlet-api is 6.0.0 and some extra bits are needed for the appengine-maven-plugin

...
    <dependency>
      <groupId>jakarta.servlet</groupId>
      <artifactId>jakarta.servlet-api</artifactId>
      <version>6.0.0</version>
      <type>jar</type>
      <scope>provided</scope>
    </dependency>
...
  <build>
    ...
      <plugin>
        <groupId>com.google.cloud.tools</groupId>
        <artifactId>appengine-maven-plugin</artifactId>
        <version>2.8.6</version>
        <configuration>
          <projectId>test-site</projectId>
          <version>GCLOUD_CONFIG</version>
          <cloudSdkVersion>544.0.0</cloudSdkVersion>
        </configuration>
      </plugin>
    </plugins>
  </build>

Let me know if I missed something you were looking for.

1 Like

Thanks for the update… Can repro now!

I’ve now have a fix.

Not sure if it will make it for the next week Cloud CLI release, so we might need to wait until Feb 24th to have it there… I hope this is fine.

I’m just glad I was able to help. I’ll watch the release notes for the fix and make my updates after. No rush from my side. Thanks for the quick attention!

So we made it in time for a Cloud CLI release next Tuesday! You can try again next week.

The appengine:stop maven and gradle plugins change will also be in next version, as soon as chore(main): release 2.8.7-SNAPSHOT by release-please[bot] · Pull Request #1069 · GoogleCloudPlatform/appengine-plugins · GitHub is merged and a new plugin is pushed, but I do not control this process.

1 Like

The issue is caused by recent Cloud SDK versions missing some Jetty security classes needed for local testing with security constraints. The short-term fix is forcing cloudSdkVersion 544.0.0 in your appengine-maven-plugin and using Java 21 with Jakarta EE 10. There is no official fix yet, and no open bug is widely acknowledged. For now, continue using this configuration for local testing and deploy with the latest SDK if possible.

Confirmed. Local testing works again, and I’m deployed with the latest Java and associated EE support. Thanks again for the quick response.

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.