spring-data-neo4j

Spring Data Neo4j - Quick Reference

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "spring-data-neo4j" with this command: npx skills add claude-dev-suite/claude-dev-suite/claude-dev-suite-claude-dev-suite-spring-data-neo4j

Spring Data Neo4j - Quick Reference

Deep Knowledge: Use mcp__documentation__fetch_docs with technology: spring-data-neo4j for comprehensive documentation.

Dependencies

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-neo4j</artifactId> </dependency>

Configuration

application.yml

spring: neo4j: uri: bolt://localhost:7687 authentication: username: neo4j password: ${NEO4J_PASSWORD}

data: neo4j: database: mydb # Neo4j 4.0+

Graph Concepts

┌─────────────────────────────────────────────────────────────┐ │ Graph Model │ │ │ │ ┌─────────┐ ┌─────────┐ │ │ │ Person │─────FOLLOWS────────▶│ Person │ │ │ │ (John) │ │ (Jane) │ │ │ └────┬────┘ └────┬────┘ │ │ │ │ │ │ WORKS_AT WORKS_AT │ │ │ │ │ │ ▼ ▼ │ │ ┌─────────┐ ┌─────────┐ │ │ │ Company │◀─────KNOWS───────────│ Person │ │ │ │ (Acme) │ │ (Bob) │ │ │ └─────────┘ └─────────┘ │ │ │ │ Nodes: Person, Company │ │ Relationships: FOLLOWS, WORKS_AT, KNOWS │ └─────────────────────────────────────────────────────────────┘

Node Entities

@Node("Person") public class Person {

@Id
@GeneratedValue
private Long id;

private String name;

private String email;

private LocalDate birthDate;

// Outgoing relationship
@Relationship(type = "FOLLOWS", direction = Direction.OUTGOING)
private Set&#x3C;Person> following = new HashSet&#x3C;>();

// Incoming relationship
@Relationship(type = "FOLLOWS", direction = Direction.INCOMING)
private Set&#x3C;Person> followers = new HashSet&#x3C;>();

// Relationship with properties
@Relationship(type = "WORKS_AT")
private WorksAt employment;

// Multiple relationships of same type
@Relationship(type = "KNOWS")
private List&#x3C;Knows> connections = new ArrayList&#x3C;>();

}

@Node("Company") public class Company {

@Id
@GeneratedValue
private Long id;

private String name;

private String industry;

@Relationship(type = "WORKS_AT", direction = Direction.INCOMING)
private Set&#x3C;Person> employees = new HashSet&#x3C;>();

}

Relationship Entities

@RelationshipProperties public class WorksAt {

@Id
@GeneratedValue
private Long id;

@TargetNode
private Company company;

private String position;

private LocalDate startDate;

private LocalDate endDate;

private BigDecimal salary;

}

@RelationshipProperties public class Knows {

@Id
@GeneratedValue
private Long id;

@TargetNode
private Person person;

private String context;  // "work", "school", "family"

private LocalDate since;

private Integer trustLevel;

}

Repository Pattern

public interface PersonRepository extends Neo4jRepository<Person, Long> {

// Derived queries
Optional&#x3C;Person> findByEmail(String email);

List&#x3C;Person> findByNameContaining(String name);

// Custom Cypher queries
@Query("MATCH (p:Person)-[:FOLLOWS]->(f:Person) WHERE p.id = $personId RETURN f")
List&#x3C;Person> findFollowing(Long personId);

@Query("MATCH (p:Person)&#x3C;-[:FOLLOWS]-(f:Person) WHERE p.id = $personId RETURN f")
List&#x3C;Person> findFollowers(Long personId);

@Query("""
    MATCH (p:Person {id: $personId})-[:FOLLOWS*2..3]->(fof:Person)
    WHERE NOT (p)-[:FOLLOWS]->(fof) AND p &#x3C;> fof
    RETURN DISTINCT fof
    LIMIT $limit
    """)
List&#x3C;Person> findFriendsOfFriends(Long personId, int limit);

@Query("""
    MATCH (p1:Person {id: $person1Id}), (p2:Person {id: $person2Id}),
          path = shortestPath((p1)-[:KNOWS*]-(p2))
    RETURN path
    """)
List&#x3C;Person> findShortestPath(Long person1Id, Long person2Id);

// Aggregations
@Query("""
    MATCH (p:Person)-[:WORKS_AT]->(c:Company)
    RETURN c.name as company, count(p) as employeeCount
    ORDER BY employeeCount DESC
    """)
List&#x3C;CompanyStats> getCompanyStats();

// With relationship properties
@Query("""
    MATCH (p:Person)-[w:WORKS_AT]->(c:Company)
    WHERE p.id = $personId
    RETURN p, w, c
    """)
Person findWithEmployment(Long personId);

}

public interface CompanyRepository extends Neo4jRepository<Company, Long> {

@Query("""
    MATCH (c:Company)&#x3C;-[:WORKS_AT]-(p:Person)
    WHERE c.id = $companyId
    RETURN p
    """)
List&#x3C;Person> findEmployees(Long companyId);

}

Neo4jTemplate Operations

@Service @RequiredArgsConstructor public class GraphService {

private final Neo4jTemplate neo4jTemplate;
private final Neo4jClient neo4jClient;

// Save operations
public Person savePerson(Person person) {
    return neo4jTemplate.save(person);
}

// Find by ID
public Optional&#x3C;Person> findById(Long id) {
    return neo4jTemplate.findById(id, Person.class);
}

// Custom queries with Neo4jClient
public List&#x3C;Map&#x3C;String, Object>> findMutualConnections(Long person1Id, Long person2Id) {
    return neo4jClient.query("""
        MATCH (p1:Person {id: $person1Id})-[:KNOWS]-(mutual:Person)-[:KNOWS]-(p2:Person {id: $person2Id})
        RETURN mutual.name as name, mutual.email as email
        """)
        .bind(person1Id).to("person1Id")
        .bind(person2Id).to("person2Id")
        .fetch()
        .all()
        .stream()
        .toList();
}

// Create relationship
public void createFollowRelationship(Long followerId, Long followeeId) {
    neo4jClient.query("""
        MATCH (a:Person {id: $followerId}), (b:Person {id: $followeeId})
        MERGE (a)-[:FOLLOWS]->(b)
        """)
        .bind(followerId).to("followerId")
        .bind(followeeId).to("followeeId")
        .run();
}

// Delete relationship
public void removeFollowRelationship(Long followerId, Long followeeId) {
    neo4jClient.query("""
        MATCH (a:Person {id: $followerId})-[r:FOLLOWS]->(b:Person {id: $followeeId})
        DELETE r
        """)
        .bind(followerId).to("followerId")
        .bind(followeeId).to("followeeId")
        .run();
}

// Complex graph traversal
public List&#x3C;Person> findInfluencers(int minFollowers) {
    return neo4jClient.query("""
        MATCH (p:Person)&#x3C;-[:FOLLOWS]-(follower:Person)
        WITH p, count(follower) as followerCount
        WHERE followerCount >= $minFollowers
        RETURN p
        ORDER BY followerCount DESC
        """)
        .bind(minFollowers).to("minFollowers")
        .fetchAs(Person.class)
        .mappedBy((typeSystem, record) -> {
            // Custom mapping if needed
            return neo4jTemplate.findById(
                record.get("p").asNode().id(),
                Person.class
            ).orElse(null);
        })
        .all()
        .stream()
        .filter(Objects::nonNull)
        .toList();
}

}

Projections

// Interface projection public interface PersonSummary { String getName(); String getEmail(); int getFollowerCount(); }

// DTO projection public record PersonDto( Long id, String name, String email, List<String> followerNames ) {}

public interface PersonRepository extends Neo4jRepository<Person, Long> {

@Query("""
    MATCH (p:Person)
    WHERE p.id = $id
    OPTIONAL MATCH (p)&#x3C;-[:FOLLOWS]-(f:Person)
    RETURN p.id as id, p.name as name, p.email as email,
           collect(f.name) as followerNames
    """)
Optional&#x3C;PersonDto> findPersonDtoById(Long id);

}

Transactions

@Service @Transactional public class SocialNetworkService {

private final PersonRepository personRepository;
private final Neo4jClient neo4jClient;

@Transactional
public void transferFollowers(Long fromPersonId, Long toPersonId) {
    // All operations in single transaction
    neo4jClient.query("""
        MATCH (from:Person {id: $fromId})&#x3C;-[r:FOLLOWS]-(follower:Person)
        MATCH (to:Person {id: $toId})
        CREATE (follower)-[:FOLLOWS]->(to)
        DELETE r
        """)
        .bind(fromPersonId).to("fromId")
        .bind(toPersonId).to("toId")
        .run();
}

@Transactional(readOnly = true)
public List&#x3C;Person> findRecommendations(Long personId) {
    // Read-only transaction
    return personRepository.findFriendsOfFriends(personId, 10);
}

}

Reactive Support

public interface ReactivePersonRepository extends ReactiveNeo4jRepository<Person, Long> {

Flux&#x3C;Person> findByNameContaining(String name);

@Query("MATCH (p:Person)-[:FOLLOWS]->(f:Person) WHERE p.id = $personId RETURN f")
Flux&#x3C;Person> findFollowing(Long personId);

}

@Service public class ReactiveGraphService {

private final ReactiveNeo4jClient neo4jClient;

public Flux&#x3C;Person> streamInfluencers() {
    return neo4jClient.query("""
        MATCH (p:Person)&#x3C;-[:FOLLOWS]-(f:Person)
        WITH p, count(f) as followers
        WHERE followers > 100
        RETURN p
        ORDER BY followers DESC
        """)
        .fetchAs(Person.class)
        .all();
}

}

Testing with Testcontainers

@SpringBootTest @Testcontainers class PersonRepositoryTest {

@Container
static Neo4jContainer&#x3C;?> neo4j = new Neo4jContainer&#x3C;>("neo4j:5")
    .withAdminPassword("password");

@DynamicPropertySource
static void neo4jProperties(DynamicPropertyRegistry registry) {
    registry.add("spring.neo4j.uri", neo4j::getBoltUrl);
    registry.add("spring.neo4j.authentication.username", () -> "neo4j");
    registry.add("spring.neo4j.authentication.password", neo4j::getAdminPassword);
}

@Autowired
private PersonRepository personRepository;

@Test
void shouldFindFollowers() {
    Person john = personRepository.save(new Person("John"));
    Person jane = personRepository.save(new Person("Jane"));

    john.getFollowing().add(jane);
    personRepository.save(john);

    List&#x3C;Person> followers = personRepository.findFollowers(jane.getId());
    assertThat(followers).contains(john);
}

}

Best Practices

Do Don't

Model relationships explicitly Use arrays for connections

Use projections for partial data Fetch entire graph

Index frequently queried properties Query without indexes

Use MERGE for idempotent creates CREATE duplicates

Limit traversal depth Unbounded graph traversals

Production Checklist

  • Indexes on lookup properties

  • Constraints for uniqueness

  • Connection pooling configured

  • Transaction timeouts set

  • Query profiling enabled

  • Backup strategy defined

  • Cluster configuration (if HA)

  • Memory settings tuned

  • Monitoring enabled

  • Cypher query optimization

When NOT to Use This Skill

  • Raw Cypher queries - Consult Neo4j documentation directly

  • Relational data - Use spring-data-jpa for tabular data

  • Document storage - Use spring-data-mongodb

  • Simple key-value - Use spring-data-redis

Anti-Patterns

Anti-Pattern Problem Solution

Fetching entire graph Memory issues Use projections, limit depth

CREATE instead of MERGE Duplicate nodes Use MERGE for idempotent creates

Unbounded traversals Performance issues Add depth limits

Missing indexes Slow lookups Create indexes on lookup properties

Arrays for relationships Loses graph benefits Use proper @Relationship

Ignoring relationship direction Wrong query results Specify INCOMING/OUTGOING

Quick Troubleshooting

Problem Diagnostic Fix

Connection refused Check Neo4j running Start Neo4j, check bolt URI

Node not persisted Check @Node annotation Add annotation, verify ID

Relationship missing Check @Relationship Verify type and direction

Slow Cypher Use PROFILE/EXPLAIN Add indexes, optimize query

Circular reference Check entity graph Use @Relationship carefully

Reference Documentation

  • Spring Data Neo4j Reference

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

spring-data-neo4j

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

cron-scheduling

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

token-optimization

No summary provided by upstream source.

Repository SourceNeeds Review