Java SDK
Java Quick Start
WoWSQL Java SDK — Get started in minutes
Get your API keys: In your WoWSQL project dashboard, go to Settings → API to find your anon key (wowsql_anon_...) and service role key (wowsql_service_...). The anon key is safe for client-side use (respects Row Level Security). The service role key bypasses RLS and should only be used server-side.
Installation
Official package: View on Maven Central → (com.wowsql:wowsql-sdk-java). Kotlin uses a separate artifact: com.wowsql:wowsql-sdk.
Requirements: Java 11 or later. Classes live in the com.wowsql package (WOWSQLClient, ProjectAuthClient, WOWSQLStorage, WOWSQLSchema, WOWSQLException); response models are in com.wowsql.models.
Maven:
<dependency>
<groupId>com.wowsql</groupId>
<artifactId>wowsql-sdk-java</artifactId>
<version>3.10.0</version>
</dependency>
Gradle:
implementation 'com.wowsql:wowsql-sdk-java:3.10.0'
Initialize Client
The SDK talks to PostgREST at /rest/v1/ under the hood. Your project URL resolves to https://myproject.wowsqlconnect.com.
import com.wowsql.WOWSQLClient;
import com.wowsql.models.QueryResponse;
import java.util.*;
// Initialize client — URL resolves to https://myproject.wowsqlconnect.com
// SDK communicates with PostgREST at /rest/v1/
WOWSQLClient client = new WOWSQLClient(
"https://myproject.wowsqlconnect.com",
"wowsql_anon_your_key_here"
);
// Always use select() before get()/execute()
QueryResponse users = client.table("users").select("*").limit(10).get();
Query Data
Basic Queries
// Get all records
QueryResponse users = client.table("users").select("*").get();
// With filters
QueryResponse adults = client.table("users")
.select("*")
.filter("age", "gte", 18)
.get();
// Select specific columns
QueryResponse names = client.table("users").select("id", "name").get();
Advanced Queries
// Multiple filters with sorting
QueryResponse activeUsers = client.table("users")
.select("*")
.filter("status", "eq", "active")
.filter("age", "gt", 18)
.order("created_at", "desc")
.limit(20)
.offset(0)
.get();
System.out.println("Found " + activeUsers.getCount() + " users");
for (Map<String, Object> user : activeUsers.getData()) {
System.out.println(user.get("name"));
}
Filter Helper Methods
// Using helper methods: eq, gt, gte, lt, lte, like, in, between
QueryResponse users = client.table("users")
.eq("status", "active")
.gt("age", 18)
.like("email", "%@gmail.com")
.in("role", Arrays.<Object>asList("admin", "moderator"))
.between("created_at", "2024-01-01", "2024-12-31")
.get();
GROUP BY and Aggregates
Basic GROUP BY
// Group by category with counts
QueryResponse result = client.table("products")
.select("category", "COUNT(*) as count", "AVG(price) as avg_price")
.groupBy("category")
.get();
// Group by date
QueryResponse result2 = client.table("orders")
.select("DATE(created_at) as date", "COUNT(*) as orders", "SUM(total) as revenue")
.groupBy("DATE(created_at)")
.order("date", "desc") // or .orderBy("date", true) — true = descending
.get();
HAVING Clause
QueryResponse result = client.table("products")
.select("category", "COUNT(*) as count")
.groupBy("category")
.having("COUNT(*)", "gt", 10)
.get();
CRUD Operations
Create Record
import com.wowsql.models.CreateResponse;
Map<String, Object> row = new HashMap<>();
row.put("name", "John Doe");
row.put("email", "john@example.com");
row.put("age", 30);
CreateResponse result = client.table("users").create(row);
System.out.println("Created user with ID: " + result.getId());
Update Record
client.table("users").update(123, Map.of("email", "newemail@example.com"));
System.out.println("User updated successfully");
Delete Record
client.table("users").delete(123);
System.out.println("User deleted successfully");
Get Single Record by ID
Map<String, Object> user = client.table("users").getById(123);
System.out.println("User: " + user.get("name"));
Get First Record
Map<String, Object> first = client.table("users")
.filter("status", "eq", "active")
.first();
if (first != null) {
System.out.println("First active user: " + first.get("name"));
}
Filter Operators
Available operators for filtering data:
| Operator | Description | Example (Java) |
|---|---|---|
eq | Equals | .filter("status", "eq", "active") |
neq | Not equals | .filter("status", "neq", "deleted") |
gt | Greater than | .filter("age", "gt", 18) |
gte | Greater than or equal | .filter("age", "gte", 18) |
lt | Less than | .filter("price", "lt", 100) |
lte | Less than or equal | .filter("price", "lte", 100) |
like | Pattern matching | .filter("name", "like", "john") |
in | IN operator | .in("category", Arrays.asList("electronics", "books")) |
not_in | NOT IN | .notIn("status", Arrays.asList("deleted", "archived")) |
between | BETWEEN | .between("price", 10, 100) |
Error Handling
import com.wowsql.WOWSQLException;
try {
QueryResponse users = client.table("users").select("*").get();
System.out.println("Success: " + users.getData().size() + " users");
} catch (WOWSQLException e) {
Integer status = e.getStatusCode(); // may be null for network errors
if (status != null && status == 401) System.out.println("Authentication failed — check your API key");
else if (status != null && status == 404) System.out.println("Table or resource not found");
else System.out.println("Error (" + status + "): " + e.getMessage());
}
Authentication
Auth endpoints are served at /auth/v1/. Use the anon key for client-side auth operations.
Sign Up & Sign In
import com.wowsql.ProjectAuthClient;
import com.wowsql.models.AuthResponse;
ProjectAuthClient auth = new ProjectAuthClient(
"https://myproject.wowsqlconnect.com",
"wowsql_anon_your_key_here"
);
// Sign up — POST /auth/v1/signup
AuthResponse result = auth.signUp("user@example.com", "SecurePassword123", "John Doe", null);
System.out.println("Access token: " + result.getSession().getAccessToken());
// Sign in — POST /auth/v1/login (returns a session, or a 2FA challenge)
AuthResponse login = auth.signIn("user@example.com", "SecurePassword123");
if (login.isRequires2fa()) {
// TOTP is on for this user: finish with verifyTwoFactorLogin (see Two-Factor Authentication)
System.out.println("2FA required, method: " + login.getMethod());
} else {
System.out.println("Access token: " + login.getSession().getAccessToken());
}
CAPTCHA (Turnstile)
Optional. Only required when dashboard Attack Protection → Turnstile is enabled.
Create a Cloudflare Turnstile widget for your app domain and paste the site key and secret in Attack Protection (WoWSQL does not share a platform widget).
GET /auth/v1/settings returns your captcha.site_key. Render Turnstile with that key, then pass the token.
Omit it when captcha is off — existing overloads are unchanged.
auth.signUp("user@example.com", "SecurePassword123", null, null, turnstileToken);
auth.signIn("user@example.com", "SecurePassword123", turnstileToken);
auth.forgotPassword("user@example.com", turnstileToken);
auth.sendOtp("user@example.com", null, "login", turnstileToken);
auth.sendMagicLink("user@example.com", "login", turnstileToken);
auth.resendVerification("user@example.com", turnstileToken);
Password Reset
auth.forgotPassword("user@example.com");
auth.resetPassword("reset_token_from_email", "NewSecurePassword123");
OTP Authentication
Added in com.wowsql:wowsql-sdk-java:3.9.1.
Email: sendOtp(email, purpose) / verifyOtp(email, otp, purpose, newPassword).
Phone SMS: sendOtpPhone(phone, purpose) / verifyOtpPhone(phone, otp, purpose, newPassword).
Provide exactly one channel (email or phone). Phone OTP requires Auth → SMS / Phone OTP (Twilio, Fast2SMS, or MSG91).
purpose: login, signup, or password_reset (pass newPassword for password reset).
// Email OTP
auth.sendOtp("user@example.com", "login");
AuthResponse emailLogin = (AuthResponse) auth.verifyOtp(
"user@example.com", "123456", "login", null
);
System.out.println("Logged in: " + emailLogin.getUser().getId());
// Phone OTP (SMS) — E.164, e.g. +15551234567
auth.sendOtpPhone("+15551234567", "login");
AuthResponse phoneLogin = (AuthResponse) auth.verifyOtpPhone(
"+15551234567", "123456", "login", null
);
System.out.println("Logged in: " + phoneLogin.getUser().getId());
// Optional: combined overload — exactly one of email or phone
auth.sendOtp(null, "+15551234567", "login");
AuthResponse login = (AuthResponse) auth.verifyOtp(
null, "+15551234567", "123456", "login", null
);
OAuth Authentication
Setup checklist — do this once per provider, per project:
1. Dashboard → Project → Auth → Providers: enable the provider, paste Client ID and Client Secret (no leading/trailing spaces).
2. Copy the Redirect URI shown: https://<slug>.wowsqlconnect.com/auth/v1/oauth/<provider>/callback and register it in your provider's developer console.
3. The callback URL is handled entirely by WoWSQL — your app does not send an API key on it. WoWSQL exchanges the code and redirects to your frontend_redirect_uri with ?access_token=&refresh_token=.
// ── Step 1: Server-side — get the authorization URL ─────────────────────────
// Returns a Map with keys: authorization_url, provider, backend_callback_url, frontend_redirect_uri
Map<String, String> oauth = auth.getOAuthAuthorizationUrl(
"google",
"https://yourapp.com/auth/callback"
);
// Redirect the browser to oauth.get("authorization_url")
// Google calls: https://<slug>.wowsqlconnect.com/auth/v1/oauth/google/callback
// WoWSQL exchanges the code and redirects to your frontend_redirect_uri.
// ── Step 2: Frontend — read tokens from the redirect ────────────────────────
// WoWSQL redirects to: https://yourapp.com/auth/callback?access_token=...&refresh_token=...
// ── (Advanced) Manual code exchange — only if you handle the callback yourself
AuthResponse result = auth.exchangeOAuthCallback(
"google",
"authorization_code_from_callback",
"https://yourapp.com/auth/callback" // optional; the server exchanges the code with the project callback URL
);
System.out.println("OAuth login successful: " + result.getUser().getId());
Session and Token Refresh
Token refresh calls POST /auth/v1/refresh-token with the stored refresh token and stores the new token pair. From 3.10.0 every SDK uses this route; before 3.10.0 the TypeScript and Java SDKs called routes that do not exist, and Flutter and Rust had no refresh method.
// Current user → GET /auth/v1/me (uses the stored access token)
AuthUser me = auth.getUser();
// Refresh tokens → POST /auth/v1/refresh-token (uses the stored refresh token)
AuthResponse refreshed = auth.refreshToken();
System.out.println(refreshed.getSession().getAccessToken());
// Read or restore the stored session
Map<String, String> tokens = auth.getSession();
auth.setSession("saved_access_token", "saved_refresh_token");
// Sign out → POST /auth/v1/logout
auth.logout();
Two-Factor Authentication (TOTP)
Added in 3.10.0. When a user has TOTP two-factor authentication enabled, sign-in does not return a session. It returns a challenge instead: requires_2fa is true and a short-lived pending_token is set (SDK field names are in the example). Finish the sign-in with the verify call, using a 6-digit code from the authenticator app or one of the user's backup codes. Status, setup, enable, disable and recovery-code calls run as the signed-in user: the SDK sends your API key and the stored access token. Enabling 2FA and rotating recovery codes return new backup codes once; show them to the user and do not store them in plain text.
// 1. Enrol (user is signed in; the stored access token is used)
Map<String, Object> setup = auth.setupTotp(); // POST /auth/v1/2fa/setup-totp
// Show setup.get("qr_code_url") (a PNG data URI when available) or setup.get("otpauth_uri")
Map<String, Object> enabled = auth.enableTotp("123456"); // POST /auth/v1/2fa/verify-and-enable
System.out.println(enabled.get("backup_codes")); // shown once
// 2. Sign in when 2FA is on
AuthResponse login = auth.signIn("user@example.com", "SecurePassword123");
if (login.isRequires2fa()) {
// verifyTwoFactorLogin(pendingToken, code, backupCode) → POST /auth/v1/2fa/verify-login
AuthResponse done = auth.verifyTwoFactorLogin(login.getPendingToken(), "123456", null);
System.out.println(done.getSession().getAccessToken());
}
// 3. Manage
Map<String, Object> status = auth.getTwoFactorStatus(); // enabled, method
Map<String, Object> rotated = auth.regenerateRecoveryCodes("123456");
auth.disableTotp("123456", null); // or disableTotp(null, "backup-code")
Settings, Anonymous Sign-in, Identity Linking and Email Change
Added in 3.10.0. Anonymous sign-in and identity linking must be enabled in the project Auth settings (anonymous_auth_enabled, manual_linking_enabled); otherwise the server rejects the call. Linking and email change run as the signed-in user.
// Public auth settings → GET /auth/v1/settings
Map<String, Object> settings = auth.getSettings();
// Anonymous sign-in → POST /auth/v1/sign-in-anonymous
AuthResponse anon = auth.signInAnonymous();
// Link another OAuth identity to the signed-in user → POST /auth/v1/oauth/{provider}/link
auth.linkOAuthIdentity("github", "authorization_code_from_callback", "https://yourapp.com/auth/callback");
// Change email → POST /auth/v1/update-email
auth.updateEmail("new@example.com");
Storage Operations
Initialize Storage
Storage endpoints are served at /storage/v1/. The SDK storage client itself calls /api/v1/storage/projects/{project_slug}/… on your project host (buckets, files, stats).
import com.wowsql.WOWSQLStorage;
import com.wowsql.models.StorageFile;
import java.util.Map;
WOWSQLStorage storage = new WOWSQLStorage(
"https://myproject.wowsqlconnect.com",
"wowsql_anon_your_key_here"
);
File Operations
// Upload a local file: uploadFromPath(filePath, bucketName, folder)
// The third argument is the folder (sent as ?folder=); the file keeps its own name.
storage.uploadFromPath("path/to/document.pdf", "documents", "uploads"); // → uploads/document.pdf
// Public URL (public buckets): getPublicUrl(bucketName, path)
String url = storage.getPublicUrl("documents", "uploads/document.pdf");
System.out.println("Public URL: " + url);
// Download bytes, or save straight to disk
byte[] bytes = storage.download("documents", "uploads/document.pdf");
storage.downloadToFile("documents", "uploads/document.pdf", "/tmp/document.pdf");
// List files: listFiles(bucketName, prefix)
List<StorageFile> files = storage.listFiles("documents", "uploads/");
for (StorageFile file : files) {
System.out.println(file.getPath() + ": " + file.getSize() + " bytes");
}
// Delete file: deleteFile(bucketName, path)
storage.deleteFile("documents", "uploads/document.pdf");
// Check usage (GET .../stats). getQuota() calls .../quota and returns a StorageQuota
// (getTotalFiles(), getTotalSizeBytes(), getTotalSizeGb(), getFileTypes()).
Map<String, Object> stats = storage.getStats();
System.out.println(stats.get("total_files") + " files, " + stats.get("total_size_gb") + " GB used");
Buckets
import com.wowsql.models.StorageBucket;
StorageBucket avatars = storage.createBucket("avatars", true); // name, isPublic
List<StorageBucket> buckets = storage.listBuckets();
StorageBucket bucket = storage.getBucket("avatars");
storage.updateBucket("avatars", Map.of("public", false));
storage.deleteBucket("avatars");
Download or View File
Via REST:
- Upload:
POST /storage/v1/object/{bucket}/{path} - Download:
GET /storage/v1/object/{bucket}/{path} - List:
POST /storage/v1/object/list/{bucket} - Public URL:
GET /storage/v1/object/public/{bucket}/{path}
Realtime
Subscribe to INSERT / UPDATE / DELETE, broadcast ephemeral events, and track presence. Uses the same anon or service_role key as REST.
WebSocket URL: wss://<project>.wowsqlconnect.com/realtime/v1/websocket?apikey=<wowsql_anon_... or wowsql_service_...>. Auth is always the query param. Enable a table first with POST /realtime/v1/enable and body {"schema_name":"public","table_name":"messages"}. Channel send / presence do not need a Postgres trigger. Payloads are capped at 64 KiB. Missing key closes 4001; invalid key closes 4003 (do not reconnect). Multi-replica deployments need REDIS_URL.
Runnable unsub = client.realtime().subscribe("public", "messages", "*", change -> {
System.out.println(change.get("event") + " " + change.get("new"));
});
RealtimeChannel channel = client.realtime().channel("chat");
channel.onBroadcast("typing", msg -> System.out.println(msg.get("payload")));
channel.onPresence(msg -> System.out.println(channel.presenceState()));
channel.subscribe(status -> {});
channel.track(Map.of("user", "alice"));
channel.send("typing", Map.of("user", "alice"));
unsub.run();
channel.unsubscribe();
client.realtime().disconnect();
Schema DDL (WOWSQLSchema)
Requires service role key. createTable requires the primary key column to be type UUID. Use "auto_increment": true on that column for DEFAULT gen_random_uuid(). Raw CREATE TABLE via execute SQL cannot use SERIAL PRIMARY KEY (or similar integer PK). Existing tables are unchanged.
import com.wowsql.WOWSQLSchema;
WOWSQLSchema schema = new WOWSQLSchema("myproject", "wowsql_service_your_key_here");
List<Map<String, Object>> columns = new ArrayList<>();
Map<String, Object> idCol = new HashMap<>();
idCol.put("name", "id");
idCol.put("type", "UUID");
idCol.put("auto_increment", true);
columns.add(idCol);
Map<String, Object> titleCol = new HashMap<>();
titleCol.put("name", "title");
titleCol.put("type", "TEXT");
columns.add(titleCol);
schema.createTable("items", columns, "id");