NoClassDefFoundError while using cypher-dsl in an unmanaged server extension

I am trying to import neo4j-cypher-dsl into an unmanaged server extension. I can start the extension but when I call the REST API method that uses DSL code I see the following error in the log:

javax.servlet.ServletException: org.glassfish.jersey.server.ContainerException: java.lang.NoClassDefFoundError: org/neo4j/cypherdsl/core/Cypher
	at org.glassfish.jersey.servlet.WebComponent.serviceImpl(WebComponent.java:410) ~[jersey-container-servlet-core-2.34.jar:?]
   ...

This is likely to be caused by this line of code in org.neo4j.cypherdsl.core.Cypher:

static final ResourceBundle MESSAGES = ResourceBundle.getBundle("org.neo4j.cypherdsl.core.messages");

Has anyone come across this problem?

Hi @AlbertGevorgyan Michael here, author of the Neo4j Cypher-DSL.

This works just fine. Did you remember to package up the Cypher-DSL with your code? Cypher-DSL is not a module / library of the Neo4j database. It is distributed as a separate artifact. So if you want to use it inside an extension or plugin, you have to package it up. I usually suggest the maven-assembly-plugin. An alternative is the Shade-Plugin.

Here's a working example, first the pom.xml with the dependencies. Notice how Neo4j core is in scope provided (as the extension is deployed into Neo4j, hence, the api is provided) and how Cypher-DSL is compile scope (Mavens) default:

<?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>org.example</groupId>
	<artifactId>some-extension</artifactId>
	<version>1.0-SNAPSHOT</version>

	<properties>
		<maven.compiler.release>11</maven.compiler.release>
		<neo4j.version>4.4.3</neo4j.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.neo4j</groupId>
			<artifactId>neo4j</artifactId>
			<version>${neo4j.version}</version>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>org.neo4j</groupId>
			<artifactId>neo4j-cypher-dsl</artifactId>
			<version>2022.0.0</version>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.9.0</version>
			</plugin>

			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-assembly-plugin</artifactId>
				<version>3.1.1</version>
				<configuration>
					<descriptorRefs>
						<descriptorRef>jar-with-dependencies</descriptorRef>
					</descriptorRefs>
				</configuration>
				<executions>
					<execution>
						<id>make-assembly</id>
						<phase>package</phase>
						<goals>
							<goal>single</goal>
						</goals>
					</execution>
				</executions>
			</plugin>
		</plugins>
	</build>
</project>

Notice the configuration of the assembler plugin. I use the predefined descriptorRef jar-with-dependencies.

The resulting JAR will contain the repackaged library. As I said, shading is another option (which can also rename packages).

The 3rd option would be packaging your code as you normally would (thin jar), add it as plugin to neo4j plus the cypher-dsl.jar.

Anyhow, here's the example code for reference:

package org.neo4j.examples.server.unmanaged;

import static org.neo4j.cypherdsl.core.Cypher.parameter;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;

import org.neo4j.cypherdsl.core.Cypher;
import org.neo4j.cypherdsl.core.Expression;
import org.neo4j.cypherdsl.core.Functions;
import org.neo4j.cypherdsl.core.renderer.Configuration;
import org.neo4j.cypherdsl.core.renderer.Renderer;
import org.neo4j.dbms.api.DatabaseManagementService;
import org.neo4j.string.UTF8;

@Path("/helloworld")
public class HelloWorldResource {
	private final DatabaseManagementService dbms;

	public HelloWorldResource(@Context DatabaseManagementService dbms) {
		this.dbms = dbms;
	}

	@GET
	@Produces(MediaType.TEXT_PLAIN)
	@Path("/{nodeId}")
	public Response hello(@PathParam("nodeId") long nodeId) {

		// Let's make sure we trigger the messages ;)
		try {
			Cypher.returning((Expression) null);
		} catch (Exception e) {
			System.out.println("oops… " + e.getMessage());
		}

		var anyNode = Cypher.anyNode("n");
		var statement = Cypher.match(anyNode)
			.where(Functions.id(anyNode).isEqualTo(parameter("nodeId").withValue(nodeId))).returning(anyNode).build();
		var cypher = Renderer.getRenderer(Configuration.prettyPrinting()).render(statement);
		return Response.status(Status.OK).entity(UTF8.encode("Here's a nice query\n\n" + cypher + "\n\n")).build();
	}
}

when called, the result looks like this:

curl --user neo4j:secret localhost:7474/examples/unmanaged/helloworld/4711
Here's a nice query

MATCH (n)
WHERE id(n) = $nodeId
RETURN n

and the server prints the output I had in the code demonstrating that the message resource is not the cause of such a thing (oops… At least one expressions to return is required.).

I hope this helps.

Thanks for the quick answer. Translating this to Gradle: the the Shadow plugin works for me.

On a side note: is it actually recommended to use Cypher in extensions given that the Core API is also available?