Full Tutorial
Java SDK
The FraiseQL Java SDK is a schema authoring SDK: you define GraphQL types, queries, and mutations in Java using annotations, and the FraiseQL compiler generates an optimized GraphQL API backed by your SQL views.
Installation
Section titled “Installation”<dependency> <groupId>dev.fraiseql</groupId> <artifactId>fraiseql-sdk</artifactId> <version>2.0.0</version></dependency>Gradle
Section titled “Gradle”dependencies { implementation 'dev.fraiseql:fraiseql-sdk:2.0.0'}Requirements: Java 17+
Core Concepts
Section titled “Core Concepts”FraiseQL Java provides four annotations that map your Java types to GraphQL schema constructs:
| Annotation | GraphQL Equivalent | Purpose |
|---|---|---|
@FraiseType | type | Define a GraphQL output type |
@FraiseInput | input | Define a GraphQL input type |
@FraiseQuery | Query field | Wire a query to a SQL view |
@FraiseMutation | Mutation field | Define a mutation |
Defining Types
Section titled “Defining Types”Use @FraiseType to define GraphQL output types. Fields map 1:1 to columns in your backing SQL view’s .data JSONB object.
package com.example.schema;
import dev.fraiseql.annotations.*;import dev.fraiseql.scalars.*;
@FraiseTypepublic class User { public ID id; public String username; public Email email; public String bio; // nullable public DateTime createdAt;}package com.example.schema;
import dev.fraiseql.annotations.*;import dev.fraiseql.scalars.*;import java.util.List;
@FraiseTypepublic class Post { public ID id; public String title; public Slug slug; public String content; public boolean isPublished; public DateTime createdAt; public DateTime updatedAt; // Nested types are composed from views at compile time public User author; public List<Comment> comments;}package com.example.schema;
import dev.fraiseql.annotations.*;import dev.fraiseql.scalars.*;
@FraiseTypepublic class Comment { public ID id; public String content; public DateTime createdAt; public User author;}Built-in Scalars
Section titled “Built-in Scalars”FraiseQL provides semantic scalars that add validation and documentation:
import dev.fraiseql.scalars.*;
ID // UUID, auto-serializedEmail // Validated email addressSlug // URL-safe slugDateTime // ISO 8601 datetimeURL // Validated URLDefining Inputs
Section titled “Defining Inputs”Use @FraiseInput to define GraphQL input types for mutations:
package com.example.schema;
import dev.fraiseql.annotations.*;import dev.fraiseql.scalars.*;
@FraiseInputpublic class CreateUserInput { @Validate(minLength = 3, maxLength = 50, pattern = "^[a-z0-9_]+$") public String username;
@Validate(required = true) public Email email;
public String bio; // nullable, optional}package com.example.schema;
import dev.fraiseql.annotations.*;import dev.fraiseql.scalars.*;
@FraiseInputpublic class CreatePostInput { @Validate(minLength = 1, maxLength = 200) public String title;
public String content; public ID authorId; public boolean isPublished = false;}Defining Queries
Section titled “Defining Queries”Use @FraiseQuery to wire queries to SQL views. The sqlSource attribute names the view that backs this query:
package com.example.schema;
import dev.fraiseql.annotations.*;import dev.fraiseql.scalars.*;import java.util.List;import java.util.Optional;
public class Queries {
// List query — maps to SELECT * FROM v_post WHERE <args> @FraiseQuery(sqlSource = "v_post") public List<Post> posts( Boolean isPublished, ID authorId, int limit, int offset ) { return null; }
// Single-item query — maps to SELECT * FROM v_post WHERE id = $1 @FraiseQuery(sqlSource = "v_post", idArg = "id") public Optional<Post> post(ID id) { return null; }
// Query with row filter for current user's posts @FraiseQuery(sqlSource = "v_post", rowFilter = "author_id = {current_user_id}") public List<Post> myPosts(int limit) { return null; }}How Arguments Become WHERE Clauses
Section titled “How Arguments Become WHERE Clauses”FraiseQL’s automatic-where feature maps query arguments to SQL filters automatically. Declare an argument whose name matches a column in the backing view, and FraiseQL appends it as a WHERE clause:
query { posts(isPublished: true, authorId: "usr_01HZ3K") { ... }}Becomes:
SELECT data FROM v_postWHERE is_published = true AND author_id = 'usr_01HZ3K'LIMIT 20 OFFSET 0;No resolver code required.
Defining Mutations
Section titled “Defining Mutations”Use @FraiseMutation to define mutations that execute PostgreSQL functions:
package com.example.schema;
import dev.fraiseql.annotations.*;import dev.fraiseql.auth.*;import dev.fraiseql.scalars.*;
public class Mutations {
// FraiseQL calls: SELECT * FROM fn_create_user($1::jsonb) @FraiseMutation public User createUser(MutationContext info, CreateUserInput input) { return null; }
@FraiseMutation @Authenticated @RequiresScope("write:posts") public Post createPost(MutationContext info, CreatePostInput input) { return null; }
@FraiseMutation @Authenticated @RequiresScope("write:posts") public Post publishPost(MutationContext info, ID id) { return null; }
@FraiseMutation @Authenticated @RequiresScope("admin:posts") public boolean deletePost(MutationContext info, ID id) { return false; }}Each mutation maps to a PostgreSQL function in db/schema/03_functions/. The Java definition is the schema; the SQL function is the implementation.
Authorization
Section titled “Authorization”Use @Authenticated and @RequiresScope to protect queries and mutations:
import dev.fraiseql.auth.*;
@FraiseMutation@Authenticated@RequiresScope("write:posts")public Post createPost(MutationContext info, CreatePostInput input) { return null; }
@FraiseMutation@Authenticated@RequiresScope("admin:posts")public boolean deletePost(MutationContext info, ID id) { return false; }Middleware
Section titled “Middleware”Use @FraiseMiddleware to intercept requests and set context:
package com.example.schema;
import dev.fraiseql.annotations.*;import dev.fraiseql.middleware.*;
public class AppMiddleware {
@FraiseMiddleware public Response extractUserContext(Request request, Handler next) { if (request.getAuth() != null) { request.getContext().put("current_user_id", request.getAuth().getClaim("sub")); request.getContext().put("current_org_id", request.getAuth().getClaim("org_id")); } return next.handle(request); }}Spring Boot Integration
Section titled “Spring Boot Integration”package com.example;
import dev.fraiseql.spring.*;import org.springframework.context.annotation.*;
@Configuration@EnableFraiseQLpublic class FraiseQLConfig { // FraiseQL auto-discovers @FraiseType, @FraiseQuery, @FraiseMutation classes // in your Spring component scan path}fraiseql: database-url: ${DATABASE_URL} jwt-secret: ${JWT_SECRET} schema-packages: com.example.schemaBuild and Serve
Section titled “Build and Serve”-
Build the schema — compiles Java annotations to the FraiseQL IR:
Terminal window fraiseql compileExpected output:
✓ Schema compiled: 3 types, 2 queries, 2 mutations✓ Views validated against database✓ Build complete: schema.json -
Serve the API:
Terminal window fraiseql runExpected output:
✓ FraiseQL 2.0.0 running on http://localhost:8080/graphql✓ GraphQL Playground at http://localhost:8080/graphql
Testing
Section titled “Testing”FraiseQL provides a test client that compiles your schema and runs queries against a real database:
import dev.fraiseql.testing.*;import org.junit.jupiter.api.*;import static org.junit.jupiter.api.Assertions.*;
class SchemaTest { private FraiseQLTestClient client;
@BeforeEach void setUp() { client = FraiseQLTestClient.builder() .schemaPackage("com.example.schema") .databaseUrl(System.getenv("FRAISEQL_TEST_DATABASE_URL")) .build(); }
@Test void testCreateAndFetchPost() throws Exception { var result = client.mutate(""" mutation { createPost(input: { title: "Hello", content: "World" }) { id title isPublished } } """);
var post = result.get("createPost"); assertEquals("Hello", post.get("title")); assertFalse((Boolean) post.get("isPublished")); }}Next Steps
Section titled “Next Steps”Custom Queries
Security
Other SDKs