Java SDK
The FraiseQL Java SDK is a schema authoring SDK: you define GraphQL types using annotations and register queries and mutations using a fluent builder API, and the FraiseQL compiler generates an optimized GraphQL API backed by your SQL views.
Installation
Section titled “Installation”<dependency> <groupId>com.fraiseql</groupId> <artifactId>fraiseql-java</artifactId> <version>2.1.0</version></dependency>Gradle
Section titled “Gradle”dependencies { implementation 'com.fraiseql:fraiseql-java:2.1.0'}Requirements: Java 17+
Core Concepts
Section titled “Core Concepts”FraiseQL Java uses annotations for type definitions and a fluent builder API for queries and mutations:
| Construct | GraphQL Equivalent | Purpose |
|---|---|---|
@GraphQLType | type | Mark a class as a GraphQL output type |
@GraphQLInput | input | Mark a class as a GraphQL input type |
@GraphQLField | field metadata | Configure field names, nullability, scopes |
FraiseQL.query(name)... | Query field | Wire a query to a SQL view |
FraiseQL.mutation(name)... | Mutation field | Define a mutation |
Defining Types
Section titled “Defining Types”Annotate classes with @GraphQLType and fields with @GraphQLField. The type attribute on @GraphQLField maps to FraiseQL scalar types:
package schema;
import com.fraiseql.core.*;
@GraphQLTypepublic class User { @GraphQLField(type = "ID", nullable = false) public String id;
@GraphQLField(nullable = false) public String username;
@GraphQLField(type = "Email", nullable = false) public String email;
@GraphQLField(nullable = true) public String bio;
@GraphQLField(type = "DateTime", nullable = false) public String createdAt;}package schema;
import com.fraiseql.core.*;
@GraphQLTypepublic class Post { @GraphQLField(type = "ID", nullable = false) public String id;
@GraphQLField(nullable = false) public String title;
@GraphQLField(type = "Slug", nullable = false) public String slug;
@GraphQLField(nullable = false) public String content;
@GraphQLField(nullable = false) public boolean isPublished;
@GraphQLField(type = "DateTime", nullable = false) public String createdAt;
@GraphQLField(type = "DateTime", nullable = false) public String updatedAt;}Built-in Scalars
Section titled “Built-in Scalars”Scalar type strings are provided as constants in com.fraiseql.core.Scalars:
import com.fraiseql.core.Scalars;
// Use as string literals in @GraphQLField(type = ...)Scalars.ID // "ID"Scalars.EMAIL // "Email"Scalars.SLUG // "Slug"Scalars.DATE_TIME // "DateTime"Scalars.URL // "URL"Scalars.DATE // "Date"Scalars.UUID // "UUID"Scalars.DECIMAL // "Decimal"Scalars.JSON // "Json"Field-Level Scopes
Section titled “Field-Level Scopes”Use requiresScope or requiresScopes on @GraphQLField for field-level access control:
@GraphQLTypepublic class User { @GraphQLField(type = "ID", nullable = false) public String id;
@GraphQLField( type = "Email", nullable = false, requiresScope = "read:user.email", description = "Email address — requires read:user.email scope" ) public String email;
@GraphQLField( type = Scalars.DECIMAL, nullable = true, requiresScopes = {"read:user.salary", "admin"}, description = "Salary — requires both read:user.salary and admin scopes" ) public String salary;}Role Restriction
Section titled “Role Restriction”Use @RequiresRole on a type or field to restrict access by role:
import com.fraiseql.core.RequiresRole;
@GraphQLType@RequiresRole("admin")public class AdminDashboard { @GraphQLField(nullable = false) public int totalUsers;
@GraphQLField(nullable = false) public String systemStatus;}Defining Inputs
Section titled “Defining Inputs”Annotate input classes with @GraphQLInput and fields with @GraphQLField:
package schema;
import com.fraiseql.core.*;
@GraphQLInputpublic class CreatePostInput { @GraphQLField(nullable = false) public String title;
@GraphQLField(nullable = false) public String content;
@GraphQLField(nullable = false) public boolean isPublished;}package schema;
import com.fraiseql.core.*;
@GraphQLInputpublic class UpdatePostInput { @GraphQLField(type = "ID", nullable = false) public String id;
@GraphQLField(nullable = true) public String title;
@GraphQLField(nullable = true) public String content;
@GraphQLField(nullable = true) public Boolean isPublished;}Defining Queries
Section titled “Defining Queries”Use FraiseQL.query(name) to create a QueryBuilder, chain configuration methods, and call .register() to add the query to the schema:
package schema;
import com.fraiseql.core.FraiseQL;
public class SchemaSetup { public static void register() { // Register types FraiseQL.registerTypes(User.class, Post.class); FraiseQL.registerTypes(CreatePostInput.class, UpdatePostInput.class);
// List query — maps to v_post view FraiseQL.query("posts") .returnType(Post.class) .returnsArray(true) .sqlSource("v_post") .arg("isPublished", "Boolean") .arg("limit", "Int", 20) .arg("offset", "Int", 0) .description("Fetch posts, optionally filtered by published status.") .register();
// Single-item query — maps to v_post with id filter FraiseQL.query("post") .returnType(Post.class) .sqlSource("v_post") .arg("id", "ID") .description("Fetch a single post by ID.") .register(); }}How Arguments Become WHERE Clauses
Section titled “How Arguments Become WHERE Clauses”Query arguments matching columns in the backing view become SQL WHERE clauses automatically. See SQL Patterns → Automatic WHERE Clauses for details.
Defining Mutations
Section titled “Defining Mutations”Use FraiseQL.mutation(name) to create a MutationBuilder. Each mutation maps to a PostgreSQL function using the fn_ naming convention:
package schema;
import com.fraiseql.core.FraiseQL;import java.util.List;
public class SchemaSetup { public static void register() { // ... type and query registration above ...
// Maps to fn_create_post PostgreSQL function FraiseQL.mutation("createPost") .returnType(Post.class) .sqlSource("fn_create_post") .arg("input", "CreatePostInput") .operation("insert") .invalidatesViews(List.of("v_post")) .description("Create a new blog post.") .register();
// Maps to fn_publish_post PostgreSQL function FraiseQL.mutation("publishPost") .returnType(Post.class) .sqlSource("fn_publish_post") .arg("id", "ID") .operation("update") .invalidatesViews(List.of("v_post")) .description("Publish a draft post.") .register();
// Maps to fn_delete_post PostgreSQL function FraiseQL.mutation("deletePost") .returnType(Post.class) .sqlSource("fn_delete_post") .arg("id", "ID") .operation("delete") .invalidatesViews(List.of("v_post")) .description("Soft-delete a post.") .register(); }}The SQL Side
Section titled “The SQL Side”Mutations wire to PostgreSQL functions returning mutation_response. Views return (id UUID, data JSONB). See SQL Patterns for complete table, view, and function templates.
Transport Annotations
Section titled “Transport Annotations”Transport annotations are optional. Omit them to serve an operation via GraphQL only. Use @FraiseQLQuery and @FraiseQLMutation method-level annotations with restPath and restMethod attributes. gRPC endpoints are auto-generated when [grpc] is enabled — no per-operation annotation needed. See gRPC Transport.
import io.fraiseql.sdk.*;
@FraiseQLQuery( sqlSource = "v_post", restPath = "/posts", restMethod = "GET")public List<Post> posts(@Default("10") int limit) { return null; }
@FraiseQLQuery( sqlSource = "v_post", restPath = "/posts/{id}", restMethod = "GET")public Post post(UUID id) { return null; }
@FraiseQLMutation( sqlSource = "create_post", operation = "CREATE", restPath = "/posts", restMethod = "POST")public Post createPost(String title, UUID authorId) { return null; }Path parameters in restPath (e.g., {id}) must match method parameter names exactly. A mismatch produces a compile-time error. Duplicate (method, path) pairs are also rejected at compile time.
JWT Claim Injection
Section titled “JWT Claim Injection”Use .inject(map) on a query or mutation builder to inject server-side values from the JWT without exposing them as client-controlled arguments:
import java.util.Map;
// Automatically inject the authenticated user's ID from the JWT sub claimFraiseQL.query("myPosts") .returnType(Post.class) .returnsArray(true) .sqlSource("v_post") .arg("limit", "Int", 20) .inject(Map.of("author_id", "jwt:sub")) .description("Fetch the current user's posts.") .register();Build and Export
Section titled “Build and Export”-
Register all types and operations in your application startup:
src/main/java/Main.java public class Main {public static void main(String[] args) throws Exception {SchemaSetup.register();FraiseQL.exportSchema("schema.json");System.out.println("Schema exported to schema.json");}} -
Compile with the CLI:
Terminal window fraiseql compile --schema schema.json -
Serve the API:
Terminal window fraiseql run
Next Steps
Section titled “Next Steps”- SDK Overview — how compile-time authoring works
- SQL Patterns — view and function conventions
- Your First API — full tutorial
- All SDKs — compare languages