{"id":16261078,"url":"https://github.com/fuxingloh/airtable","last_synced_at":"2025-08-01T22:32:56.703Z","repository":{"id":42039507,"uuid":"182396891","full_name":"fuxingloh/airtable","owner":"fuxingloh","description":"A lightweight Java 8 Airtable API client for https://airtable.com/api with all features implemented.","archived":false,"fork":false,"pushed_at":"2022-04-16T10:55:37.000Z","size":153,"stargazers_count":21,"open_issues_count":3,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-12-01T03:50:26.169Z","etag":null,"topics":["airtable","airtable-api","java","java-api"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/fuxingloh.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-04-20T11:21:28.000Z","updated_at":"2024-11-03T18:43:43.000Z","dependencies_parsed_at":"2022-08-12T03:10:14.076Z","dependency_job_id":null,"html_url":"https://github.com/fuxingloh/airtable","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fuxingloh%2Fairtable","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fuxingloh%2Fairtable/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fuxingloh%2Fairtable/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fuxingloh%2Fairtable/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fuxingloh","download_url":"https://codeload.github.com/fuxingloh/airtable/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":228413826,"owners_count":17915909,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["airtable","airtable-api","java","java-api"],"created_at":"2024-10-10T16:40:25.817Z","updated_at":"2024-12-06T04:54:51.109Z","avatar_url":"https://github.com/fuxingloh.png","language":"Java","funding_links":[],"categories":["java"],"sub_categories":[],"readme":"# Airtable Java API Interface\n\nThis library support all features available in https://airtable.com/api. \n\n[![CI](https://github.com/fuxingloh/airtable/actions/workflows/ci.yml/badge.svg?branch=master\u0026event=push)](https://github.com/fuxingloh/airtable/actions/workflows/ci.yml)\n[![codecov](https://codecov.io/gh/fuxingloh/airtable/branch/master/graph/badge.svg?token=GF8N55YZQ1)](https://codecov.io/gh/fuxingloh/airtable)\n[![maven-central](https://img.shields.io/maven-central/v/dev.fuxing/airtable-api)](https://search.maven.org/artifact/dev.fuxing/airtable-api)\n\n# Features:\n* Supports the new batch API \n* Supports all fields\n* Supports all features exposed in https://airtable.com/api (as of 2020/01/20)\n* Build in pagination support\n* Heavily documented (javadoc)\n* Fluent Query Builder for type safe query building\n  * `AirtableFormula` fluent builder\n  * `AirtableTable.QuerySpec` fluent builder\n* Lightweight\n  * `commons-lang3` mainly for FastDateFormat, java 8 version is non thread-safe\n  * `fluent-hc` to perform REST call\n  * `jackson-databind` for handling JSON data\n* Proper airtable \u0026 client exception handling\n  * `AirtableApiException` (from api service: https://api.airtable.com) \n  * `AirtableClientException` (from client: most likely your mistake)\n  * Status 429 Backoff 30 seconds auto try\n* Customizable HTTP Client (fluent-hc)\n* Custom Module: Cache using Guava\n* Custom Module: Data Mirroring (e.g. ETL, Lake, MR)\n\n# Download\nHosted in Maven Central.\n### Maven\n```xml\n\u003cdependency\u003e\n  \u003cgroupId\u003edev.fuxing\u003c/groupId\u003e\n  \u003cartifactId\u003eairtable-api\u003c/artifactId\u003e\n  \u003cversion\u003e0.3.1\u003c/version\u003e\n\u003c/dependency\u003e\n```\n### Gradle\n```groovy\ncompile group: 'dev.fuxing', name: 'airtable-api', version: '0.3.2'\n```\n\n# Example\n#### Getting the AirtableTable interface.\n```java\nAirtableApi api = new AirtableApi(\"key...\");\nAirtableTable table = api.base(\"app...\").table(\"Table Name\");\n```\n\n#### List\nQuerying and getting a list.\n\n```java\n// List with offset support\nAirtableTable.PaginationList list = table.list(querySpec -\u003e {\n    querySpec.view(\"View Name\");\n    querySpec.filterByFormula(LogicalOperator.EQ, field(\"Value\"), value(1));\n});\n\n// For next pagination\nlist.getOffset();\n```\n\n#### Iterator\nIterator with automated build in support for pagination.\n```java\ntable.iterator().forEachRemaining(record -\u003e {\n    List\u003cAttachmentField\u003e images = record.getFieldAttachmentList(\"Images\");\n    String name = record.getFieldString(\"Name\");\n});\n```\n\n#### Query Spec Builder\nAll list querystring is supported with functional fluent formula builder.\n\n```java\nList\u003cAirtableRecord\u003e list = table.list(query -\u003e {\n    // Localisation\n    query.cellFormat(\"string\")\n    query.timeZone(\"Asia/Singapore\");\n    query.userLocale(\"af\");\n\n    // Data filtering\n    query.view(\"View Name\");\n    query.fields(\"a\", \"b\");\n    \n    // Sorting\n    query.sort(\"field-name\");\n    query.sort(\"field-name\", \"desc\");\n    \n    // Pagingation\n    query.pageSize(50);\n    query.maxRecords(1000);\n    query.offset(\"rec...\");\n    \n    // Vanilla String Formula: NOT({F} = '')\n    query.filterByFormula(\"NOT({F} = '')\");\n    \n    // Compile time Typesafe Query Formula\n    // {Value}=1\n    query.filterByFormula(LogicalOperator.EQ, AirtableFormula.Object.field(\"Value\"), value(1));\n    \n    // 1+2\n    query.filterByFormula(NumericOperator.ADD, AirtableFormula.Object.value(1), AirtableFormula.Object.value(2))\n    \n    // {f1}=(AND(1,{f2}))\n    query.filterByFormula(LogicalOperator.EQ, field(\"f1\"), parentheses(LogicalFunction.AND, value(1), field(\"f2\")));\n});\n\n```\n#### Getting an existing record\n```java\nAirtableRecord record = table.get(\"rec...\");\n```\n\n#### Creating a new record\n```java\nAirtableRecord record = new AirtableRecord();\nrecord.putField(\"Name\", \"Posted\");\n\n// Attachment support\nCollaboratorField collaborator = new CollaboratorField();\ncollaborator.setEmail(\"me@email.com\");\nrecord.putField(\"Collaborator\", collaborator);\n\nAttachmentField attachment = new AttachmentField();\nattachment.setUrl(\"https://upload.wikimedia.org/wikipedia/commons/5/56/Wiki_Eagle_Public_Domain.png\");\nrecord.putFieldAttachments(\"Attachments\", Collections.singletonList(field));\n\nrecord = table.post(record);\n```\n\n#### Patching an existing record\n```java\nAirtableRecord record = new AirtableRecord();\nrecord.setId(\"rec...\");\nrecord.putField(\"Name\", \"Patched\");\n\nrecord = table.patch(record);\n```\n\n#### Replacing an existing record\n```java\nAirtableRecord record = new AirtableRecord();\nrecord.setId(\"rec...\");\nrecord.putField(\"Name\", \"Replaced Entirely\");\n\nrecord = table.put(record);\n```\n\n#### Deleting an existing record\n```java\ntable.delete(\"rec...\");\n```\n\n### 429 Auto Retry\nAuto retry is enabled by default. To disable it, you can create an `Executor` without retry.\n```java\nExecutor executor = AirtableExecutor.newInstance(false);\nAirtableApi api = new AirtableApi(\"key...\", executor);\nAirtableTable table = api.base(\"app...\").table(\"Table Name\");\n```\n# Cache Module\n\u003e Use Airtable as your main database with heavy caching strategy. \n\nFor many read heavy applicaiton, status 429; too many request can be problematic when developing for speed. Cache is a read-only interface that will ignore ignorable `AirtableApiException` (429, 500, 502, 503).  \n\n#### Creating an airtable cache.\nAirtableCache uses a different HTTPClient with more concurent connection pool. RetryStrategy is also ignored.\n```java\nAirtableCache cache = AirtableCache.create(builder -\u003e builder\n        .apiKey(System.getenv(\"AIRTABLE_API_KEY\"))\n        .app(\"app3h0gjxLX3Jomw8\")\n        .table(\"Test Table\")\n        // Optional cache control\n        .withGet(maxRecords, cacheDuration, cacheTimeUnit)\n        .withQuery(maxRecords, cacheDuration, cacheTimeUnit)\n);\n```\n\n#### Get record by id\nGet will always attempt to get the latest record from airtable server.\u003cbr\u003e\nFallback read from cache will only happen if any of the ignorable exception is thrown.\n```java\nAirtableRecord record = cache.get(\"rec0W9eGVAFSy9Chb\");\n```\n\n#### Get records results by query spec\nQuery will always attempt to get the latest result from airtable server.\u003cbr\u003e\nFallback read from cache will only happen if any of the ignorable exception is thrown.\u003cbr\u003e\nThe cache key used will be the querystring.\n```java\nList\u003cAirtableRecord\u003e results = cache.query(querySpec -\u003e {\n    querySpec.filterByFormula(LogicalOperator.EQ, field(\"Name\"), value(\"Name 1\"));\n});\n```\n\n#### Gradle Dependencies\n```groovy\ncompile group: 'dev.fuxing', name: 'airtable-api', version: '0.3.2'\ncompile group: 'dev.fuxing', name: 'airtable-cache', version: '0.3.2'\n```\n# Mirror Module\n\u003e Use Airtable as your stateless database view for EDA.\n\nFor many applications:\n* You need a quick and dirty way to look at data from your database. \n* Your product manager don't know how to use SQL. \n* You don't want to manually create scripts and generate them. \n* You like Airtable and how simple it is. \n* You want to use Airtable blocks to generate Analytic.\n* You want visibility of internal data.\n* You are irritated from the requests your pm requires. (sort by?, you want me to filter WHAT?, why can't you learn how to use group by!)\n\n#### Implementation\n```java\n// Example Database\nDatabase database = new Database();\n\nAirtableMirror mirror = new AirtableMirror(table, field(\"PrimaryKey in Airtable\")) {\n    @Override\n    protected Iterator\u003cAirtableRecord\u003e iterator() {\n        // Provide an iterator of all your records to mirror over to Airtable.\n        Iterator\u003cDatabase.Data\u003e iterator = database.iterator();\n\n        return new Iterator\u003cAirtableRecord\u003e() {\n            @Override\n            public boolean hasNext() {\n                return iterator.hasNext();\n            }\n\n            @Override\n            public AirtableRecord next() {\n                Database.Data data = iterator.next();\n\n                // Map into AirtableRecord\n                AirtableRecord record = new AirtableRecord();\n                record.putField(\"Name\", data.name);\n                record.putField(\"Checkbox\", data.checkbox);\n                ...\n                return record;\n            }\n        };\n    }\n\n    // Whether a record from your (the iterator) is still same from airtable.\n    protected boolean same(AirtableRecord fromIterator, AirtableRecord fromAirtable) {\n        // You might want to add a timestamp to simplify checking\n        String left = fromIterator.getFieldString(\"Name\");\n        String right = fromAirtable.getFieldString(\"Name\");\n        return Objects.equals(left, right);\n    }\n\n    // Whether a row in airtable still exists in your database\n    protected boolean has(String fieldValue) {\n        return database.get(fieldValue) != null;\n    }\n};\n// Async run it every 6 hours.\nScheduledExecutorService ses = Executors.newSingleThreadScheduledExecutor();\nses.scheduleAtFixedRate(mirror, 0, 6, TimeUnit.HOURS);\n```\n\n#### Gradle Dependencies\n```groovy\ncompile group: 'dev.fuxing', name: 'airtable-api', version: '0.3.2'\ncompile group: 'dev.fuxing', name: 'airtable-mirror', version: '0.3.2'\n```\n\n# Testing\n\nhttps://airtable.com/shrTMCxjhQIF2ZJDe is used to run test against a real instance\n# Publishing\n\n- `./gradlew uploadArchives closeAndPromoteRepository`\n- Requires `NEXUS_USERNAME` \u0026 `NEXUS_PASSWORD` \u0026 PGP key.\n\n\u003e Since PGP key is required, and it's my key that I rather it not being stored on a server. I will be publishing\n\u003e manually instead.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffuxingloh%2Fairtable","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffuxingloh%2Fairtable","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffuxingloh%2Fairtable/lists"}