As modern applications increasingly rely on complex relationships between data entities, traditional relational databases can become cumbersome to manage. Graph databases offer a natural way to model these relationships, making them ideal for social networks, recommendation engines, and more.

In this article, we explore how to implement a graph database in Java using Eclipse JNoSQL and Jakarta Data. You’ll learn how to configure the environment, model graph entities, and perform graph operations in a modern, cloud-native Java setup.

What Are Graph Databases?

A graph database uses graph structures with nodes, edges, and properties to represent and store data. Unlike relational databases, which use tables and foreign keys, graph databases are optimized for querying relationships.

Popular graph databases include Neo4j, Amazon Neptune, and JanusGraph. For this article, we’ll use Apache TinkerPop-compatible graph databases via Eclipse JNoSQL, an abstraction framework that simplifies working with NoSQL databases in Java.

What Is Eclipse JNoSQL?

Eclipse JNoSQL is a framework that provides integration between Jakarta EE (Jakarta Data, Jakarta CDI, Jakarta JSON) and NoSQL databases (key-value, column, document, and graph). It abstracts the complexities of vendor-specific APIs.

Combined with Jakarta Data, which standardizes repository-style data access in Java, JNoSQL allows seamless graph database development in an idiomatic Java way.

Setting Up the Environment

Before we start coding, we need to set up the environment.

Tools required:

  • Java 17+

  • Maven

  • A graph database (e.g., TinkerGraph – an in-memory TinkerPop implementation)

  • Eclipse IDE or any Java editor

Add dependencies to pom.xml:

xml
<dependencies>
<!-- Jakarta Data and CDI -->
<dependency>
<groupId>jakarta.data</groupId>
<artifactId>jakarta.data-api</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>org.eclipse.jnosql</groupId>
<artifactId>jnosql-graph</artifactId>
<version>1.0.0</version>
</dependency><dependency>
<groupId>org.eclipse.jnosql.communication</groupId>
<artifactId>jnosql-graph-tinkerpop</artifactId>
<version>1.0.0</version>
</dependency><!– CDI implementation (Weld for standalone) –>
<dependency>
<groupId>org.jboss.weld.se</groupId>
<artifactId>weld-se-core</artifactId>
<version>4.0.3.Final</version>
</dependency>
</dependencies>

Modeling Graph Entities in JNoSQL

Graph databases consist of vertices (nodes) and edges (relationships). With JNoSQL, you can annotate Java classes using @Entity to define vertices and @Relation to define edges.

Let’s model a simple social network with Person and relationships like friend.

java
import org.eclipse.jnosql.mapping.graph.Entity;
import org.eclipse.jnosql.mapping.graph.Id;
import org.eclipse.jnosql.mapping.graph.Relation;
import java.util.List;
import java.util.UUID;@Entity
public class Person {@Id
private String id = UUID.randomUUID().toString();private String name;@Relation(“FRIENDS_WITH”)
private List<Person> friends;// Constructors, getters, setters
public Person() {}public Person(String name) {
this.name = name;
}

// Getters and setters…
}

Creating a Repository with Jakarta Data

You can define repositories using Jakarta Data to avoid writing boilerplate queries.

java

import jakarta.data.repository.CrudRepository;

public interface PersonRepository extends CrudRepository<Person, String> {
List<Person> findByName(String name);
}

This repository interface allows you to query the graph database without implementing methods manually.

Writing the Configuration

JNoSQL uses CDI for injecting services. Here’s a basic CDI setup to bootstrap the environment.

java
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.se.SeContainer;
import jakarta.enterprise.inject.se.SeContainerInitializer;
import jakarta.inject.Inject;
@ApplicationScoped
public class GraphApp {@Inject
private PersonRepository repository;public void run() {
Person alice = new Person(“Alice”);
Person bob = new Person(“Bob”);
Person carol = new Person(“Carol”);// Establish relationships
alice.setFriends(List.of(bob, carol));
bob.setFriends(List.of(carol));repository.save(alice);
repository.save(bob);
repository.save(carol);List<Person> people = repository.findByName(“Alice”);
people.forEach(p -> System.out.println(“Found: “ + p.getName()));
}public static void main(String[] args) {
try (SeContainer container = SeContainerInitializer.newInstance().initialize()) {
container.select(GraphApp.class).get().run();
}
}
}

This basic application:

  • Creates nodes (Alice, Bob, Carol)

  • Establishes edges (FRIENDS_WITH)

  • Queries a person by name

The @Relation annotation ensures that the friends are stored as edges in the graph.

Under the Hood: How TinkerGraph Works

TinkerGraph is an in-memory graph database and is ideal for development/testing. JNoSQL integrates seamlessly using the jnosql-graph-tinkerpop module. Under the hood, JNoSQL converts your Java objects into TinkerPop vertices and edges.

You can switch from TinkerGraph to Neo4j or other TinkerPop-compatible databases later with minimal code changes, keeping your application portable and cloud-native.

Querying Relationships in Graphs

You can also define custom relationship traversals or use Gremlin directly if needed. For example, to find all people connected to a given person:

java

GraphTemplate graphTemplate = // injected or configured

Optional<Person> aliceOpt = repository.findById(“alice-id”);
aliceOpt.ifPresent(alice -> {
List<Person> friends = graphTemplate.getRelations(alice, “FRIENDS_WITH”, Person.class);
friends.forEach(friend -> System.out.println(“Alice is friends with: “ + friend.getName()));
});

Here, GraphTemplate is a service provided by JNoSQL for graph-specific queries that aren’t handled by repository methods.

Best Practices and Tips

  1. Use UUIDs as IDs – Helps avoid conflicts and is globally unique.

  2. Keep relationships bidirectional only when necessary – Don’t overcomplicate the graph.

  3. Normalize relationship types – Define edge names clearly like FRIENDS_WITH, WORKS_WITH, etc.

  4. Avoid deep nested saves – Save only the top-level entity and rely on cascade saving sparingly.

  5. Switch graph backends easily – Thanks to JNoSQL’s abstraction, you can move from TinkerGraph to a production-ready solution (like JanusGraph) without changing entity or repository code.

Testing Graph Functionality

JUnit can be used to validate graph persistence and traversal.

java
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class GraphTest {@Inject
private PersonRepository repository;@Test
public void testPersonPersistence() {
Person alice = new Person(“Alice”);
repository.save(alice);List<Person> results = repository.findByName(“Alice”);
assertFalse(results.isEmpty());
assertEquals(“Alice”, results.get(0).getName());
}
}

Make sure you configure the Weld container to run JUnit tests with CDI support.

Scaling To Production Graph Databases

To scale this architecture:

  • Use JanusGraph with a persistent backend like Cassandra or HBase.

  • Use Neo4j with its Bolt driver integrated via TinkerPop.

  • Secure your CDI beans using Jakarta Security.

  • Monitor graph performance with built-in metrics and tracing.

You can switch databases by changing the configuration and dependency, as long as the database is compatible with Apache TinkerPop.

Conclusion

Implementing graph databases in Java has never been easier thanks to Eclipse JNoSQL and Jakarta Data. This combination brings the power of declarative data access, type safety, and vendor-agnostic persistence to graph-based data modeling.

In this article, we explored how to:

  • Model graph entities with JNoSQL annotations

  • Use Jakarta Data repositories to simplify access

  • Configure and run a standalone graph-enabled Java app

  • Query and manipulate graph relationships programmatically

The ability to abstract database logic and seamlessly switch between graph database implementations makes Eclipse JNoSQL a future-ready framework. It helps you build scalable, portable, and maintainable applications that are well-suited for the relationship-centric demands of modern software.

Whether you’re building a recommendation engine, fraud detection system, or social platform, adopting graph databases with JNoSQL in Java is a strategic choice that enhances data flexibility and insight.