API Records - Java SDK Documentation
Overview
The Records API provides comprehensive CRUD (Create, Read, Update, Delete) operations for collection records, along with powerful search, filtering, and authentication capabilities.
Key Features:
- Paginated list and search with filtering and sorting
- Single record retrieval with expand support
- Create, update, and delete operations
- Batch operations for multiple records
- Authentication methods (password, OAuth2, OTP)
- Email verification and password reset
- Relation expansion up to 6 levels deep
- Field selection and excerpt modifiers
Backend Endpoints:
GET /api/collections/{collection}/records- List recordsGET /api/collections/{collection}/records/{id}- View recordPOST /api/collections/{collection}/records- Create recordPATCH /api/collections/{collection}/records/{id}- Update recordDELETE /api/collections/{collection}/records/{id}- Delete recordPOST /api/batch- Batch operations
CRUD Operations
List/Search Records
Returns a paginated records list with support for sorting, filtering, and expansion.
import com.bosbase.sdk.BosBase;
BosBase pb = new BosBase("http://127.0.0.1:8090");
// Basic list with pagination
ResultList result = pb.collection("posts").getList(1, 50, false, null, null, null, null, null, null);
System.out.println(result.page); // 1
System.out.println(result.perPage); // 50
System.out.println(result.totalItems); // 150
System.out.println(result.totalPages); // 3
System.out.println(result.items); // Array of records
Advanced List with Filtering and Sorting
// Filter and sort
ResultList result = pb.collection("posts").getList(
1, 50, false,
"created >= '2022-01-01 00:00:00' && status = 'published'",
"-created,title", // DESC by created, ASC by title
"author,categories",
null, null, null, null, null
);
// Filter with operators
ResultList result2 = pb.collection("posts").getList(
1, 50, false,
"title ~ 'javascript' && views > 100",
"-views",
null, null, null, null, null
);
Get Full List
Fetch all records at once (useful for small collections):
// Get all records
List<ObjectNode> allPosts = pb.collection("posts").getFullList(
200,
"status = 'published'",
"-created",
null, null, null, null
);
// With batch size for large collections
List<ObjectNode> allPosts = pb.collection("posts").getFullList(
200,
"-created",
null, null, null, null
);
Get First Matching Record
Get only the first record that matches a filter:
ObjectNode post = pb.collection("posts").getFirstListItem(
"slug = 'my-post-slug'",
"author,categories.tags",
null, null, null, null, null
);
View Record
Retrieve a single record by ID:
// Basic retrieval
ObjectNode record = pb.collection("posts").getOne("RECORD_ID", null, null, null, null);
// With expanded relations
ObjectNode record = pb.collection("posts").getOne(
"RECORD_ID",
"author,categories,tags",
null, null, null
);
// Nested expand
ObjectNode record = pb.collection("comments").getOne(
"COMMENT_ID",
"post.author,user",
null, null, null
);
// Field selection
ObjectNode record = pb.collection("posts").getOne(
"RECORD_ID",
null,
"id,title,content,author.name",
null, null
);
Create Record
Create a new record:
// Simple create
Map<String, Object> recordData = new HashMap<>();
recordData.put("title", "My First Post");
recordData.put("content", "Lorem ipsum...");
recordData.put("status", "draft");
ObjectNode record = pb.collection("posts").create(recordData, null, null, null);
// Create with relations
Map<String, Object> recordData = new HashMap<>();
recordData.put("title", "My Post");
recordData.put("author", "AUTHOR_ID"); // Single relation
recordData.put("categories", List.of("cat1", "cat2")); // Multiple relation
ObjectNode record = pb.collection("posts").create(recordData, null, null, null);
// Create with file upload (multipart/form-data)
Map<String, Object> recordData = new HashMap<>();
recordData.put("title", "My Post");
Map<String, List<FileAttachment>> files = new HashMap<>();
List<FileAttachment> fileList = new ArrayList<>();
// fileList.add(new FileAttachment(fileBytes, "image.jpg", "image/jpeg"));
files.put("image", fileList);
ObjectNode record = pb.collection("posts").create(recordData, files, null, null);
// Create with expand to get related data immediately
Map<String, Object> recordData = new HashMap<>();
recordData.put("title", "My Post");
recordData.put("author", "AUTHOR_ID");
ObjectNode record = pb.collection("posts").create(recordData, null, "author", null);
Update Record
Update an existing record:
// Simple update
Map<String, Object> updateData = new HashMap<>();
updateData.put("title", "Updated Title");
updateData.put("status", "published");
ObjectNode record = pb.collection("posts").update("RECORD_ID", updateData, null, null, null);
// Update with relations
Map<String, Object> updateData = new HashMap<>();
updateData.put("categories+", "NEW_CATEGORY_ID"); // Append
updateData.put("tags-", "OLD_TAG_ID"); // Remove
pb.collection("posts").update("RECORD_ID", updateData, null, null, null);
// Update with file upload
Map<String, Object> updateData = new HashMap<>();
updateData.put("title", "Updated Title");
Map<String, List<FileAttachment>> files = new HashMap<>();
List<FileAttachment> fileList = new ArrayList<>();
// fileList.add(new FileAttachment(newFileBytes, "newimage.jpg", "image/jpeg"));
files.put("image", fileList);
ObjectNode record = pb.collection("posts").update("RECORD_ID", updateData, files, null, null);
// Update with expand
Map<String, Object> updateData = new HashMap<>();
updateData.put("title", "Updated");
ObjectNode record = pb.collection("posts").update(
"RECORD_ID",
updateData,
null,
"author,categories",
null
);
Delete Record
Delete a record:
// Simple delete
pb.collection("posts").delete("RECORD_ID", null, null);
// Note: Returns 204 No Content on success
// Throws error if record doesn't exist or permission denied
Filter Syntax
The filter parameter supports a powerful query syntax:
Comparison Operators
// Equal
String filter = "status = 'published'";
// Not equal
String filter = "status != 'draft'";
// Greater than / Less than
String filter = "views > 100";
String filter = "created < '2023-01-01'";
// Greater/Less than or equal
String filter = "age >= 18";
String filter = "price <= 99.99";
String Operators
// Contains (like)
String filter = "title ~ 'javascript'";
// Equivalent to: title LIKE "%javascript%"
// Not contains
String filter = "title !~ 'deprecated'";
// Exact match (case-sensitive)
String filter = "email = 'user@example.com'";
Array Operators (for multiple relations/files)
// Any of / At least one
String filter = "tags.id ?= 'TAG_ID'"; // Any tag matches
String filter = "tags.name ?~ 'important'"; // Any tag name contains "important"
// All must match
String filter = "tags.id = 'TAG_ID' && tags.id = 'TAG_ID2'";
Logical Operators
// AND
String filter = "status = 'published' && views > 100";
// OR
String filter = "status = 'published' || status = 'featured'";
// Parentheses for grouping
String filter = "(status = 'published' || featured = true) && views > 50";
Sorting
Sort records using the sort parameter:
// Single field (ASC)
String sort = "created";
// Single field (DESC)
String sort = "-created";
// Multiple fields
String sort = "-created,title"; // DESC by created, then ASC by title
// Supported fields
String sort = "@random"; // Random order
String sort = "@rowid"; // Internal row ID
String sort = "id"; // Record ID
String sort = "fieldName"; // Any collection field
// Relation field sorting
String sort = "author.name"; // Sort by related author's name
Field Selection
Control which fields are returned:
// Specific fields
String fields = "id,title,content";
// All fields at level
String fields = "*";
// Nested field selection
String fields = "*,author.name,author.email";
// Excerpt modifier for text fields
String fields = "*,content:excerpt(200,true)";
// Returns first 200 characters with ellipsis if truncated
// Combined
String fields = "*,content:excerpt(200),author.name,author.email";
Expanding Relations
Expand related records without additional API calls:
// Single relation
String expand = "author";
// Multiple relations
String expand = "author,categories,tags";
// Nested relations (up to 6 levels)
String expand = "author.profile,categories.tags";
// Back-relations
String expand = "comments_via_post.user";
See Relations Documentation for detailed information.
Pagination Options
// Skip total count (faster queries)
ResultList result = pb.collection("posts").getList(
1, 50, true, // skipTotal = true
"status = 'published'",
null, null, null, null, null
);
// totalItems and totalPages will be -1
// Get Full List with batch processing
List<ObjectNode> allPosts = pb.collection("posts").getFullList(
200, // batch size
"-created",
null, null, null, null
);
// Processes in batches of 200 to avoid memory issues
Batch Operations
Execute multiple operations in a single transaction:
// Create a batch
BatchService batch = pb.createBatch();
// Add operations
batch.collection("posts").create(Map.of("title", "Post 1", "author", "AUTHOR_ID"), null, null, null);
batch.collection("posts").create(Map.of("title", "Post 2", "author", "AUTHOR_ID"), null, null, null);
batch.collection("tags").update("TAG_ID", Map.of("name", "Updated Tag"), null, null, null);
batch.collection("categories").delete("CAT_ID", null, null);
// Upsert (create or update based on id)
Map<String, Object> upsertData = new HashMap<>();
upsertData.put("id", "EXISTING_ID");
upsertData.put("title", "Updated Post");
batch.collection("posts").update("EXISTING_ID", upsertData, null, null, null);
// Send batch request
List<JsonNode> results = batch.send();
// Results is an array matching the order of operations
for (int i = 0; i < results.size(); i++) {
JsonNode result = results.get(i);
// Process result
}
Note: Batch operations must be enabled in Dashboard > Settings > Application.
Authentication Actions
List Auth Methods
Get available authentication methods for a collection:
ObjectNode methods = pb.collection("users").listAuthMethods(null, null, null, null);
System.out.println(methods.path("password").path("enabled").asBoolean());
System.out.println(methods.path("oauth2").path("enabled").asBoolean());
ArrayNode providers = (ArrayNode) methods.path("oauth2").path("providers");
System.out.println(methods.path("otp").path("enabled").asBoolean());
System.out.println(methods.path("mfa").path("enabled").asBoolean());
Auth with Password
ObjectNode authData = pb.collection("users").authWithPassword(
"user@example.com", // username or email
"password123",
null, null, null, null, null, null, null, null
);
// Auth data is automatically stored in pb.authStore
System.out.println(pb.authStore.isValid()); // true
System.out.println(pb.authStore.getToken()); // JWT token
System.out.println(pb.authStore.getModel().path("id").asText()); // User ID
// Access the returned data
System.out.println(authData.path("token").asText());
System.out.println(authData.path("record"));
// With expand
ObjectNode authData = pb.collection("users").authWithPassword(
"user@example.com",
"password123",
"profile", // expand
null, null, null, null, null, null, null
);
Auth with OAuth2
// Step 1: Get OAuth2 URL (usually done in UI)
ObjectNode methods = pb.collection("users").listAuthMethods(null, null, null, null);
ArrayNode providers = (ArrayNode) methods.path("oauth2").path("providers");
// Find provider in array
// Step 2: After redirect, exchange code for token
ObjectNode authData = pb.collection("users").authWithOAuth2Code(
"google", // Provider name
"AUTHORIZATION_CODE", // From redirect URL
"CODE_VERIFIER", // From step 1
"https://yourapp.com/callback", // Redirect URL
Map.of("name", "John Doe"), // Optional data for new accounts
null, null, null, null, null, null, null, null
);
Auth with OTP (One-Time Password)
// Step 1: Request OTP
ObjectNode otpRequest = pb.collection("users").requestOTP("user@example.com", null, null, null);
// Returns: { "otpId": "..." }
// Step 2: User enters OTP from email
// Step 3: Authenticate with OTP
ObjectNode authData = pb.collection("users").authWithOTP(
otpRequest.path("otpId").asText(),
"123456", // OTP from email
null, null, null, null, null, null, null, null
);
Auth Refresh
Refresh the current auth token and get updated user data:
// Refresh auth (useful on page reload)
ObjectNode authData = pb.collection("users").authRefresh(null, null, null, null);
// Check if still valid
if (pb.authStore.isValid()) {
System.out.println("User is authenticated");
} else {
System.out.println("Token expired or invalid");
}
Email Verification
// Request verification email
pb.collection("users").requestVerification("user@example.com", null, null, null);
// Confirm verification (on verification page)
pb.collection("users").confirmVerification("VERIFICATION_TOKEN", null, null, null);
Password Reset
// Request password reset email
pb.collection("users").requestPasswordReset("user@example.com", null, null, null);
// Confirm password reset (on reset page)
// Note: This invalidates all previous auth tokens
pb.collection("users").confirmPasswordReset(
"RESET_TOKEN",
"newpassword123",
"newpassword123", // Confirm
null, null, null
);
Email Change
// Must be authenticated first
pb.collection("users").authWithPassword("user@example.com", "password", null, null, null, null, null, null, null, null);
// Request email change
pb.collection("users").requestEmailChange("newemail@example.com", null, null, null);
// Confirm email change (on confirmation page)
// Note: This invalidates all previous auth tokens
pb.collection("users").confirmEmailChange(
"EMAIL_CHANGE_TOKEN",
"currentpassword",
null, null, null
);
Complete Examples
Example 1: Blog Post Search with Filters
public List<ObjectNode> searchPosts(String query, String categoryId, Integer minViews) {
StringBuilder filter = new StringBuilder();
filter.append("title ~ '").append(query).append("' || content ~ '").append(query).append("'");
if (categoryId != null) {
filter.append(" && categories.id ?= '").append(categoryId).append("'");
}
if (minViews != null) {
filter.append(" && views >= ").append(minViews);
}
ResultList result = pb.collection("posts").getList(
1, 20, false,
filter.toString(),
"-created",
"author,categories",
null, null, null, null, null
);
return result.items;
}
Example 2: User Dashboard with Related Content
public Map<String, List<ObjectNode>> getUserDashboard(String userId) {
// Get user's posts
ResultList posts = pb.collection("posts").getList(
1, 10, false,
"author = '" + userId + "'",
"-created",
"categories",
null, null, null, null, null
);
// Get user's comments
ResultList comments = pb.collection("comments").getList(
1, 10, false,
"user = '" + userId + "'",
"-created",
"post",
null, null, null, null, null
);
Map<String, List<ObjectNode>> dashboard = new HashMap<>();
dashboard.put("posts", posts.items);
dashboard.put("comments", comments.items);
return dashboard;
}
Example 3: Advanced Filtering
// Complex filter example
String filter = "(status = 'published' || featured = true) && " +
"created >= '2023-01-01' && " +
"(tags.id ?= 'important' || categories.id = 'news') && " +
"views > 100 && " +
"author.email != ''";
ResultList result = pb.collection("posts").getList(
1, 50, false,
filter,
"-views,created",
"author.profile,tags,categories",
"*,content:excerpt(300),author.name,author.email",
null, null, null, null
);
Example 4: Batch Create Posts
public List<JsonNode> createMultiplePosts(List<Map<String, Object>> postsData) {
BatchService batch = pb.createBatch();
for (Map<String, Object> postData : postsData) {
batch.collection("posts").create(postData, null, null, null);
}
List<JsonNode> results = batch.send();
// Check for failures
for (int i = 0; i < results.size(); i++) {
JsonNode result = results.get(i);
// Process result
}
return results;
}
Example 5: Pagination Helper
public List<ObjectNode> getAllRecordsPaginated(String collectionName, Map<String, Object> options) {
List<ObjectNode> allRecords = new ArrayList<>();
int page = 1;
boolean hasMore = true;
while (hasMore) {
ResultList result = pb.collection(collectionName).getList(
page, 500, true, // skipTotal for performance
(String) options.get("filter"),
(String) options.get("sort"),
(String) options.get("expand"),
null, null, null, null, null
);
allRecords.addAll(result.items);
hasMore = result.items.size() == 500;
page++;
}
return allRecords;
}
Error Handling
try {
Map<String, Object> recordData = new HashMap<>();
recordData.put("title", "My Post");
ObjectNode record = pb.collection("posts").create(recordData, null, null, null);
} catch (ClientResponseError error) {
if (error.status == 400) {
// Validation error
System.err.println("Validation errors: " + error.response);
} else if (error.status == 403) {
// Permission denied
System.err.println("Access denied");
} else if (error.status == 404) {
// Not found
System.err.println("Collection or record not found");
} else {
System.err.println("Unexpected error: " + error);
}
}
Best Practices
- Use Pagination: Always use pagination for large datasets
- Skip Total When Possible: Use
skipTotal: truefor better performance when you don’t need counts - Batch Operations: Use batch for multiple operations to reduce round trips
- Field Selection: Only request fields you need to reduce payload size
- Expand Wisely: Only expand relations you actually use
- Filter Before Sort: Apply filters before sorting for better performance
- Cache Auth Tokens: Auth tokens are automatically stored in
authStore, no need to manually cache - Handle Errors: Always handle authentication and permission errors gracefully
Related Documentation
- Collections - Collection configuration
- Relations - Working with relations
- API Rules and Filters - Filter syntax details
- Authentication - Detailed authentication guide
- Files - File uploads and handling