Neo4J 5 and Spring Boot 3 Neo4jRepository org.springframework.data.mapping.MappingException: Couldn't find PersistentEntity for type class java.util.HashMap

Hello,

I am trying to Migrate a Spring Boot 2 application from Neo4J 3 (ogm) to Spring Boot 3.2 + Neo4J 5

The project basically loads some data from a MSSQL server database into a standalone Neo4J instance. Then, Neo4j is used for querying.

Project compiles perfectly, but I am getting following error while running the project:

Caused by: org.springframework.data.mapping.MappingException: Couldn't find PersistentEntity for type class java.util.HashMap
	at org.springframework.data.mapping.context.MappingContext.getRequiredPersistentEntity(MappingContext.java:80)
	at org.springframework.data.neo4j.repository.support.Neo4jRepositoryFactory.getEntityInformation(Neo4jRepositoryFactory.java:66)
	at org.springframework.data.neo4j.repository.support.Neo4jRepositoryFactory.getTargetRepository(Neo4jRepositoryFactory.java:73)
	at org.springframework.data.repository.core.support.RepositoryFactorySupport.getRepository(RepositoryFactorySupport.java:317)
	at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.lambda$afterPropertiesSet$5(RepositoryFactoryBeanSupport.java:279)
	at org.springframework.data.util.Lazy.getNullable(Lazy.java:135)
	at org.springframework.data.util.Lazy.get(Lazy.java:113)
	at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.afterPropertiesSet(RepositoryFactoryBeanSupport.java:285)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1820)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1769)
	... 16 common frames omitted

Repo:

package com.harsh.neo4jdemo.graphdb.repo;

import org.springframework.data.neo4j.repository.Neo4jRepository;
import org.springframework.data.neo4j.repository.query.Query;
import org.springframework.stereotype.Repository;

import java.util.HashMap;
import java.util.stream.Stream;

@Repository
public interface GraphWalk4uiRepo extends Neo4jRepository<HashMap<Object, Object>, Long> {

.
.
.
.

    @Query(START_WALK_FROM_NON_PROCESS +
            UPSTREAM_APOC_CALL +
            KEEP_WALKING +
            UPSTREAM_LINKS +
            FINAL_COLLECT_UPSTREAM +
            FINAL_RETURN)
    Stream<HashMap<Object, Object>> walkGraphUpstreamStartFromNonProcess(String datasetName, String datasetApp, int maxLevel);

    @Query(START_WALK_FROM_PROCESS +
            UPSTREAM_APOC_CALL +
            KEEP_WALKING +
            UPSTREAM_LINKS +
            FINAL_COLLECT_UPSTREAM +
            FINAL_RETURN)
    Stream<HashMap<Object, Object>> walkGraphUpstreamStartFromProcess(String processName, String processApp, int maxLevel);


    @Query(START_WALK_FROM_NON_PROCESS +
            DOWNSTREAM_APOC_CALL +
            KEEP_WALKING_PROC_ONLY +
            DOWNSTREAM_LINKS_PROC_ONLY +
            FINAL_COLLECT_PROC_ONLY +
            FINAL_RETURN)
    Stream<HashMap<Object, Object>> walkGraphDownstreamStartFromNonProcessProcessesOnly(String datasetName, String datasetApp, int maxLevel);
}

pom.xml:

Spring Boot: 3.2.2

<properties>
  <neo4j.version>5.16.0</neo4j.version>
  <neo4j.apoc.version>5.16.1</neo4j.apoc.version>
</properties>
.
.
.
.
<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-neo4j</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.neo4j</groupId>
            <artifactId>neo4j</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.neo4j</groupId>
                    <artifactId>neo4j-slf4j-provider</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.neo4j.driver</groupId>
            <artifactId>neo4j-java-driver</artifactId>
            <version>5.17.0<version>
        </dependency>
        <dependency>
            <groupId>org.neo4j.procedure</groupId>
            <artifactId>apoc-core</artifactId>
           <version>${neo4j.apoc.version}</version>
        </dependency>
        <dependency>
            <groupId>org.neo4j.procedure</groupId>
            <artifactId>apoc-common</artifactId>
            <version>${neo4j.apoc.version}</version>
        </dependency>

Neo4J Config:

package com.harsh.neo4jdemo.config;

import jakarta.annotation.Nonnull;
import org.neo4j.configuration.GraphDatabaseSettings;
import org.neo4j.configuration.connectors.BoltConnector;
import org.neo4j.configuration.helpers.SocketAddress;
import org.neo4j.cypherdsl.core.renderer.Dialect;
import org.neo4j.dbms.api.DatabaseManagementService;
import org.neo4j.dbms.api.DatabaseManagementServiceBuilder;
import org.neo4j.driver.AuthTokens;
import org.neo4j.driver.Driver;
import org.neo4j.driver.GraphDatabase;
import org.neo4j.graphdb.GraphDatabaseService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.neo4j.Neo4jProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.data.neo4j.config.AbstractNeo4jConfig;
import org.springframework.data.neo4j.core.DatabaseSelectionProvider;
import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import java.nio.file.Path;
import java.util.List;

import static org.neo4j.configuration.GraphDatabaseSettings.DEFAULT_DATABASE_NAME;


@Configuration
@ComponentScan({"com.harsh.neo4jdemo.graphdb"})
@EnableNeo4jRepositories(basePackages = "com.harsh.neo4jdemo.graphdb.repo")
@EnableTransactionManagement
@Profile("!test")
public class Neo4JConfig extends AbstractNeo4jConfig {

    @Value("${neo4j.db.path}")
    private String pathname;

    private final Neo4jProperties neo4jProperties;

    @Autowired
    public Neo4JConfig(Neo4jProperties neo4jProperties) {
        this.neo4jProperties = neo4jProperties;
    }

    @Override
    @Bean
    @Nonnull
    protected DatabaseSelectionProvider databaseSelectionProvider() {
        return DatabaseSelectionProvider.createStaticDatabaseSelectionProvider(DEFAULT_DATABASE_NAME);
    }

    @Bean
    @Nonnull
    public org.neo4j.cypherdsl.core.renderer.Configuration cypherDslConfiguration() {
        return org.neo4j.cypherdsl.core.renderer.Configuration.newConfig()
                .withDialect(Dialect.NEO4J_5)
                .build();
    }

    @Override
    @Bean
    @Nonnull
    public Driver driver() {
        Neo4jProperties.Authentication authentication = neo4jProperties.getAuthentication();
        return GraphDatabase.driver(neo4jProperties.getUri(),
                AuthTokens.basic(authentication.getUsername(), authentication.getPassword()));
    }


    @Bean
    public GraphDatabaseService graphDatabaseService() {
        DatabaseManagementService managementService = new DatabaseManagementServiceBuilder(Path.of(pathname))
                .setConfig(BoltConnector.enabled, true )
                .setConfig(GraphDatabaseSettings.default_listen_address, new SocketAddress("localhost", 7687))
                .setConfig(GraphDatabaseSettings.default_advertised_address, new SocketAddress("localhost", 7687))
                .setConfig(GraphDatabaseSettings.procedure_unrestricted, List.of("apoc.create", "apoc.path"))
                .setConfig(GraphDatabaseSettings.procedure_allowlist, List.of("apoc.create", "apoc.path"))
                .build();
        GraphDatabaseService graphDatabaseService = managementService.database(DEFAULT_DATABASE_NAME);

        registerShutdownHook(managementService);
        return graphDatabaseService;
    }

    private static void registerShutdownHook(final DatabaseManagementService managementService) {
        Runtime.getRuntime().addShutdownHook(new Thread(managementService::shutdown));
    }

}

Main Class:

package com.harsh.neo4jdemo;


import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

@SpringBootApplication
@EnableJpaRepositories(basePackages = "com.harsh.neo4jdemo.graphdb.jpa.datamodel.repo")
@EntityScan({"com.harsh.neo4jdemo.graphdb.datamodel.model"})
public class GraphDbMain {
    public static void main(String[] args) {
        SpringApplication.run(GraphDbMain.class, args);
    }
}

application.properties:

spring.datasource.driver-class-name=com.microsoft.sqlserver.jdbc.SQLServerDriver
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.SQLServerDialect
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
spring.datasource.url=jdbc:sqlserver://<Some personal URL I won't mention here>
spring.datasource.username=XXXXXXXXXX
spring.datasource.password=XXXXXXXXXX
spring.neo4j.uri=bolt://localhost:7687
spring.neo4j.authentication.username=neo4j
spring.neo4j.authentication.password=password
neo4j.db.path=D:/temp/neo4j.db
server.port=8080

You can't create a repo based on a hash map. You need to create an entity with the proper attributes and annotations to be an SDN entity.

But this was working with ogm and neo4j 3.. So seems like need to change this as well then. Any ideas how should I?

You need to create a class to be used as your persistence entity. Annotate the class with @Node(“label to use”) and set the nodes’s label as its parameter.

The class requires a unique identifier annotated with @Id. You can generate your own unique identifiers, or use one SDN built in ones.

You can have a map as a class member, buts its key has to be a string or an enum. It can be an object as you show in your example code. You annotate the map’s member name with @CompositeProperty.

https://docs.spring.io/spring-data/neo4j/reference/appendix/conversions.html#custom.conversions.composite-properties

You can learn more from this link.

https://docs.spring.io/spring-data/neo4j/reference/index.html