{"id":26539840,"url":"https://github.com/berrycloud/xapi-java","last_synced_at":"2026-02-05T00:16:06.799Z","repository":{"id":65290344,"uuid":"577405178","full_name":"BerryCloud/xapi-java","owner":"BerryCloud","description":"Java xAPI model, client and tools","archived":false,"fork":false,"pushed_at":"2026-01-29T14:07:50.000Z","size":1494,"stargazers_count":17,"open_issues_count":5,"forks_count":4,"subscribers_count":3,"default_branch":"main","last_synced_at":"2026-01-29T20:10:04.428Z","etag":null,"topics":["cmi5","java","spring","tincan","xapi"],"latest_commit_sha":null,"homepage":"https://berrycloud.co.uk","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/BerryCloud.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2022-12-12T16:59:43.000Z","updated_at":"2026-01-29T14:07:53.000Z","dependencies_parsed_at":"2023-10-16T20:26:57.245Z","dependency_job_id":"a30272c4-db31-4c16-9f2f-30fe0fdffcba","html_url":"https://github.com/BerryCloud/xapi-java","commit_stats":null,"previous_names":[],"tags_count":33,"template":false,"template_full_name":null,"purl":"pkg:github/BerryCloud/xapi-java","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BerryCloud%2Fxapi-java","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BerryCloud%2Fxapi-java/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BerryCloud%2Fxapi-java/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BerryCloud%2Fxapi-java/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/BerryCloud","download_url":"https://codeload.github.com/BerryCloud/xapi-java/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BerryCloud%2Fxapi-java/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29102400,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-04T22:44:52.815Z","status":"ssl_error","status_checked_at":"2026-02-04T22:44:16.428Z","response_time":62,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["cmi5","java","spring","tincan","xapi"],"created_at":"2025-03-22T00:19:21.791Z","updated_at":"2026-02-05T00:16:06.793Z","avatar_url":"https://github.com/BerryCloud.png","language":"Java","readme":"# xAPI Java [![CodeQL](https://github.com/BerryCloud/xapi-java/actions/workflows/codeql.yml/badge.svg)](https://github.com/BerryCloud/xapi-java/actions/workflows/codeql.yml) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=BerryCloud_xapi-java\u0026metric=alert_status)](https://sonarcloud.io/summary/new_code?id=BerryCloud_xapi-java)\n\nxAPI Java helps you to create applications that send or receive xAPI [Statements](https://github.com/adlnet/xAPI-Spec/blob/master/xAPI-Data.md#statements) or [Documents](https://github.com/adlnet/xAPI-Spec/blob/master/xAPI-Data.md#10-documents).\n\nThere are two projects in this [Monorepo](https://en.wikipedia.org/wiki/Monorepo), xAPI Client and xAPI Model.\n\nBoth the xAPI Client and xAPI Model use a [fluent interface](https://en.wikipedia.org/wiki/Fluent_interface). Objects are [immutable](https://en.wikipedia.org/wiki/Immutable_object).\n\n## Requirements\n\nxAPI Java requires **Java 25 or newer**. See [CONTRIBUTING.md](CONTRIBUTING.md#prerequisites) for detailed installation instructions.\n\n### Version Compatibility\n\n- **Version 2.x** - Requires Java 25 or newer\n- **Version 1.x** - Requires Java 17 or newer\n\nIf you need to use Java 17, please use version 1.x of xAPI Java.\n\n## xAPI Java Client\n\nThe xAPI Java Client can be used by learning record providers (LRP) to communicate with learning record stores (LRS) or a system which follows the LRS requirements of one or more of the xAPI resources.\n\n### Getting started\n\nTo use the xAPI Java Client include the appropriate XML in the `dependencies` section of your `pom.xml`, as shown in the following example:\n\n```xml\n\u003cproject\u003e\n    \u003cmodelVersion\u003e4.0.0\u003c/modelVersion\u003e\n    \u003cartifactId\u003egetting-started\u003c/artifactId\u003e\n    \u003c!-- ... --\u003e\n    \u003cdependencies\u003e\n        \u003c!-- ... --\u003e\n        \u003cdependency\u003e\n            \u003cgroupId\u003edev.learning.xapi\u003c/groupId\u003e\n            \u003cartifactId\u003exapi-client\u003c/artifactId\u003e\n            \u003cversion\u003e2.0.1\u003c/version\u003e\n        \u003c/dependency\u003e\n    \u003c/dependencies\u003e\n\u003c/project\u003e\n```\n\nxAPI Java Client is available in the [Maven Central Repository](https://central.sonatype.com/artifact/dev.learning.xapi/xapi-client/).\n\n### Configuration\n\nThe xAPI Java Client has a Spring AutoConfiguration bean which picks up the following properties:\n\n| Property                  | Description                                                                     |\n| ------------------------- | ------------------------------------------------------------------------------- |\n| xapi.client.baseUrl       | The base url of the LRS endpoint                                                |\n| xapi.client.username      | Username for basic authorization header                                         |\n| xapi.client.password      | Password for basic authorization header                                         |\n| xapi.client.authorization | Authorization header (has precedence over the username and password properties) |\n\nProperties can be set using any [external configuration](https://docs.spring.io/spring-boot/docs/3.0.6/reference/htmlsingle/#features.external-config.files) method supported by Spring Boot.\n\nIf you need more specific customization (eg. your LRS needs specific headers, or you want to set the authorization header dynamically) you can create a custom configurer by implementing the `XapiClientConfigurer` interface.\n\n### Advanced Configuration\n\nThe xAPI Java Client uses the Spring WebClient. Spring WebClient has default memory limit of 256KB for buffering data. If this limit is exceeded then a DataBufferLimitException will be thrown.\n\nThe default memory limit of 256KB for buffering data could be exceeded if the LRS returns a large number of Statements or if the Statements contain attachments.\n\nIt is possible to set the memory limit for buffering data with the `spring.codec.max-in-memory-size` property.\n\nExample:\n\n```\nspring.codec.max-in-memory-size=1MB\n```\n\n### Statement Resource\n\nThe xAPI Java Client allows applications to store and fetch xAPI [Statements](https://github.com/adlnet/xAPI-Spec/blob/master/xAPI-Data.md#statements).\n\n### Getting a Statement\n\nExample:\n\n```java\nvar response = client.getStatement(r -\u003e r.id(\"4df42866-40e7-45b6-bf7c-8d5fccbdccd6\")).block();\n\nStatement statement = response.getBody();\n```\n\n### Getting a Statement with attachments\n\nExample:\n\n```java\nvar response = client.getStatement(r -\u003e r.id(\"4df42866-40e7-45b6-bf7c-8d5fccbdccd6\").attachments(true).block();\n\nStatement statement = response.getBody();\n```\n\n### Getting Statements\n\nExample:\n\n```java\nvar response = client.getStatements().block();\n\nStatementResult statementResult = response.getBody();\n\nStatement[] statements = statementResult.getStatements();\n```\n\n### Getting the next page of Statements\n\nExample:\n\n```java\nvar response = client.getStatements().block();\n\nvar moreResponse = client.getMoreStatements(r -\u003e r.more(response.getBody().getMore())).block();\n\nStatementResult moreStatementResult = moreResponse.getBody();\n\nStatement[] statements = moreStatementResult.getStatements();\n```\n\n### Getting Statements as Iterator (and processing them as a Stream)\n\nIn most cases it is preferable to use `getStatementIterator()` instead of `getStatments()` and `getMoreStatements()`.\n\nExample:\n\n```java\nvar statements = client.getStatementIterator().block();\n\n// process the first 100 Statements\nstatements.toStream().limit(100).forEach(s -\u003e {\n    // add logic here...\n  });\n```\n\n### Posting a Statement\n\nExample:\n\n```java\nclient.postStatement(\n    r -\u003e r.statement(s -\u003e s.agentActor(a -\u003e a.name(\"A N Other\").mbox(\"mailto:another@example.com\"))\n\n        .verb(Verb.ATTEMPTED)\n\n        .activityObject(o -\u003e o.id(\"https://example.com/activity/simplestatement\")\n            .definition(d -\u003e d.addName(Locale.ENGLISH, \"Simple Statement\")))))\n    .block();\n```\n\n### Posting a Statement with an attachment\n\nExample:\n\n```java\nclient.postStatement(\n    r -\u003e r.statement(s -\u003e s.agentActor(a -\u003e a.name(\"A N Other\").mbox(\"mailto:another@example.com\"))\n\n        .verb(Verb.ATTEMPTED)\n\n        .activityObject(o -\u003e o.id(\"https://example.com/activity/simplestatement\")\n            .definition(d -\u003e d.addName(Locale.ENGLISH, \"Simple Statement\")))\n\n        .addAttachment(a -\u003e a.content(\"Simple attachment\").length(17).contentType(\"text/plain\")\n            .usageType(URI.create(\"https://example.com/attachments/simplestatement\"))\n            .addDisplay(Locale.ENGLISH, \"text attachment\"))\n\n    )).block();\n```\n\n### Posting a Signed Statement\n\nExample:\n\n```java\nclient.postStatement(\n    r -\u003e r.signedStatement(s -\u003e s.agentActor(a -\u003e a.name(\"A N Other\").mbox(\"mailto:another@example.com\"))\n\n        .verb(Verb.ATTEMPTED)\n\n        .activityObject(o -\u003e o.id(\"https://example.com/activity/simplestatement\")\n            .definition(d -\u003e d.addName(Locale.ENGLISH, \"Simple Statement\"))),\n\n        keyPair.getPrivate()))\n    .block();\n```\n\n### Posting Statements\n\nExample:\n\n```java\nStatement attemptedStatement = Statement.builder()\n    .agentActor(a -\u003e a.name(\"A N Other\").mbox(\"mailto:another@example.com\")).verb(Verb.ATTEMPTED)\n    .activityObject(o -\u003e o.id(\"https://example.com/activity/simplestatement\")\n        .definition(d -\u003e d.addName(Locale.ENGLISH, \"Simple Statement\")))\n    .build();\n\nStatement passedStatement = attemptedStatement.toBuilder().verb(Verb.PASSED).build();\n\nclient.postStatements(r -\u003e r.statements(attemptedStatement, passedStatement)).block();\n```\n\n### Getting a voided Statement\n\nExample:\n\n```java\nvar response = client.getVoidedStatement(r -\u003e r.id(\"4df42866-40e7-45b6-bf7c-8d5fccbdccd6\")).block();\n\nStatement voidedStatement = response.getBody();\n```\n\n### State Resource\n\nThe xAPI Java Client allows applications to store, change, fetch, or delete [state documents](https://github.com/adlnet/xAPI-Spec/blob/master/xAPI-Communication.md#23-state-resource).\n\n#### Getting a state\n\nExample:\n\n```java\nvar response = client.getState(r -\u003e r.activityId(\"https://example.com/activity/1\")\n\n    .agent(a -\u003e a.name(\"A N Other\").mbox(\"mailto:another@example.com\"))\n\n    .registration(\"67828e3a-d116-4e18-8af3-2d2c59e27be6\")\n\n    .stateId(\"bookmark\"), String.class)\n\n    .block();\n\nString state = response.getBody();\n```\n\n#### Posting a state\n\nExample:\n\n```java\nclient.postState(r -\u003e r.activityId(\"https://example.com/activity/1\")\n\n    .agent(a -\u003e a.name(\"A N Other\").mbox(\"mailto:another@example.com\"))\n\n    .registration(\"67828e3a-d116-4e18-8af3-2d2c59e27be6\")\n\n    .stateId(\"bookmark\")\n\n    .state(\"Hello World!\"))\n\n    .block();\n```\n\n#### Putting a state\n\nExample:\n\n```java\nclient.putState(r -\u003e r.activityId(\"https://example.com/activity/1\")\n\n    .agent(a -\u003e a.name(\"A N Other\").mbox(\"mailto:another@example.com\"))\n\n    .registration(\"67828e3a-d116-4e18-8af3-2d2c59e27be6\")\n\n    .stateId(\"bookmark\")\n\n    .state(\"Hello World!\"))\n\n    .block();\n```\n\n#### Deleting a state\n\nExample:\n\n```java\nclient.deleteState(r -\u003e r.activityId(\"https://example.com/activity/1\")\n\n    .agent(a -\u003e a.name(\"A N Other\").mbox(\"mailto:another@example.com\"))\n\n    .registration(\"67828e3a-d116-4e18-8af3-2d2c59e27be6\")\n\n    .stateId(\"bookmark\"))\n\n    .block();\n```\n\n### Samples\n\nThe samples folder in this repository contains [sample applications](samples) that use the xAPI client.\n\n## xAPI Model Spring Boot Starter\n\nThe xAPI specification has strict rules for API requests/responses formatting. The xAPI Model has inbuilt validation for all of these rules. However, if you plan to use the xAPI Model, you should keep in mind that some activity providers do not fully conform to these rules.\n\nIn some cases it may be desirable to turn off some or all of the rules in order to be compatible with a wider range of xAPI activity providers. However, it should be noted that doing this is in violation of the xAPI specification.\n\nThe xAPI Model Spring Boot Starter package provides an easy way to turn on/off these validation rules.\n\n### Getting started\n\nTo use the xAPI Model Spring Boot Starter include the appropriate XML in the `dependencies` section of your `pom.xml`, as shown in the following example:\n\n```xml\n\u003cdependency\u003e\n  \u003cgroupId\u003edev.learning.xapi\u003c/groupId\u003e\n  \u003cartifactId\u003exapi-model-spring-boot-starter\u003c/artifactId\u003e\n  \u003cversion\u003e2.0.1\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\nxAPI Model Spring Boot Starter is available in the [Maven Central Repository](https://central.sonatype.com/artifact/dev.learning.xapi/xapi-model-spring-boot-starter/).\n\n### Configuration\n\nThe xAPI Model Spring Boot Starter has a Spring AutoConfiguration bean which picks up the following properties:\n\n| Property                                 | Description                                                                |\n| ---------------------------------------- | -------------------------------------------------------------------------- |\n| xapi.model.validateJson                  | Fail on trailing JSON tokens                                               |\n| xapi.model.validateProperties            | Fail on unknown JSON properties                                            |\n| xapi.model.validateNullValues            | Fail on null JSON properties                                               |\n| xapi.model.validateLiterals              | Fail on number and boolean JSON properties defined as string               |\n| xapi.model.validateObjectType            | Fail on invalid JSON objectType property                                   |\n| xapi.model.validateLocale                | Fail on invalid Locale strings                                             |\n| xapi.model.validateTimestamp             | Fail on negative zero timezone offsets                                     |\n| xapi.model.validateActivityDefinition    | Fail on invalid xAPI ActivityDefinition (missing properties)               |\n| xapi.model.validateActor                 | Fail on invalid xAPI Actor (missing or multiple identifiers)               |\n| xapi.model.validateAuthority             | Fail on invalid xAPI Authority object                                      |\n| xapi.model.validateUriScheme             | Fail on invalid xAPI URI property (missing scheme)                         |\n| xapi.model.validateMbox                  | Fail on invalid xAPI mbox property (invalid email or missing prefix)       |\n| xapi.model.validateLocaleNotUndetermined | Fail on invalid xAPI locale property (locale is undetermined)              |\n| xapi.model.validateScaledScore           | Fail on invalid xAPI scaledScore property (out of -1 - 1 range)            |\n| xapi.model.validateScore                 | Fail on invalid xAPI Score (raw score is out of min/max range)             |\n| xapi.model.validateStatementPlatform     | Fail on invalid xAPI context.platform (if present object must be Activity) |\n| xapi.model.validateStatementRevision     | Fail on invalid xAPI context.revision (if present object must be Activity) |\n| xapi.model.validateStatementListIds      | Fail on invalid xAPI statement List (conflicting statement ids)            |\n| xapi.model.validateStatementVerb         | Fail on invalid xAPI voided statement (object must be StatemnetReference)  |\n| xapi.model.validateUuidVariant           | Fail on invalid xAPI UUID property (must be UUID variant 4)                |\n\nThe default value is **TRUE** for all of the above properties.\n\n## xAPI Java Model\n\nThe xAPI model can be used by clients that send xAPI data or by servers that receive xAPI data.\n\n**The xAPI Model JAR is approximately 120 KB.** This makes it suitable for simple projects that only require basic serialisation and deserialisation.\n\n### Getting started\n\nTo use the xAPI Model include the appropriate XML in the `dependencies` section of your `pom.xml`, as shown in the following example:\n\n```xml\n\u003cproject\u003e\n    \u003cmodelVersion\u003e4.0.0\u003c/modelVersion\u003e\n    \u003cartifactId\u003egetting-started\u003c/artifactId\u003e\n    \u003c!-- ... --\u003e\n    \u003cdependencies\u003e\n        \u003c!-- ... --\u003e\n        \u003cdependency\u003e\n            \u003cgroupId\u003edev.learning.xapi\u003c/groupId\u003e\n            \u003cartifactId\u003exapi-model\u003c/artifactId\u003e\n            \u003cversion\u003e2.0.1\u003c/version\u003e\n        \u003c/dependency\u003e\n    \u003c/dependencies\u003e\n\u003c/project\u003e\n```\n\nxAPI Model is available in the [Maven Central Repository](https://central.sonatype.com/artifact/dev.learning.xapi/xapi-model/).\n\n### Creating a statement\n\nExample:\n\n```java\nStatement statement = Statement.builder()\n\n    .agentActor(a -\u003e a.name(\"A N Other\").mbox(\"mailto:another@example.com\"))\n\n    .verb(Verb.ATTEMPTED)\n\n    .activityObject(o -\u003e o.id(\"https://example.com/activity/simplestatement\")\n        .definition(d -\u003e d.addName(Locale.ENGLISH, \"Simple Statement\")))\n\n    .build();\n```\n\n### Deserializing Statements\n\nThe Jackson ObjectMapper can be used to deserialize statements into Java objects.\n\nExample:\n\n```java\nString json = \"\"\"\n    {\n        \"actor\":{\n            \"objectType\":\"Agent\",\n            \"name\":\"A N Other\",\n            \"mbox\":\"mailto:another@example.com\"\n        },\n        \"verb\":{\n            \"id\":\"http://adlnet.gov/expapi/verbs/attempted\",\n            \"display\":{\n                \"und\":\"attempted\"\n            }\n        },\n        \"object\":{\n            \"objectType\":\"Activity\",\n            \"id\":\"https://example.com/activity/simplestatement\",\n            \"definition\":{\n                \"name\":{\n                \"en\":\"Simple Statement\"\n                }\n            }\n        }\n    }\"\"\";\n\nStatement statement = objectMapper.readValue(json, Statement.class);\n```\n\n### Serializing Statements\n\nThe Jackson ObjectMapper can be used to serialize Statement objects into JSON.\n\nExample:\n\n```java\n\nStatement statement = Statement.builder()\n    .agentActor(a -\u003e a.name(\"A N Other\").mbox(\"mailto:another@example.com\")).verb(Verb.ATTEMPTED)\n    .activityObject(o -\u003e o.id(\"https://example.com/activity/simplestatement\")\n        .definition(d -\u003e d.addName(Locale.ENGLISH, \"Simple Statement\")))\n    .build();\n\nString json = objectMapper.writeValueAsString(statement);\n\n```\n\n### Creating a new statement using an existing statement as template\n\nExample:\n\n```java\n\nStatement passed = Statement.builder()\n    .agentActor(a -\u003e a.name(\"A N Other\").mbox(\"mailto:another@example.com\")).verb(Verb.PASSED)\n    .activityObject(o -\u003e o.id(\"https://example.com/activity/simplestatement\")\n        .definition(d -\u003e d.addName(Locale.ENGLISH, \"Simple Statement\")))\n    .build();\n\nStatement completed = passed.toBuilder().verb(Verb.COMPLETED).build();\n\n```\n\n### Validating Statements\n\nStatements can be validated programmatically.\n\nExample:\n\n```java\nValidator validator = Validation.buildDefaultValidatorFactory().getValidator();\n\nStatement statement = Statement.builder()\n\n    .agentActor(a -\u003e a.name(\"A N Other\").mbox(\"mailto:another@example.com\"))\n\n    .activityObject(o -\u003e o.id(\"https://example.com/xapi/activity/simplestatement\"))\n\n    .build();\n\nSet\u003cConstraintViolation\u003cStatement\u003e\u003e constraintViolations = validator.validate(statement);\n\nSystem.out.println(constraintViolations)\n\n// Prints [ConstraintViolationImpl{interpolatedMessage='must not be null', propertyPath=verb, rootBeanClass=class dev.learning.xapi.model.Statement, messageTemplate='{jakarta.validation.constraints.NotNull.message}'}]\n\n```\n\nStatements can also be validated when they are received by a method in a REST controller. The following example requires Spring MVC and the Hibernate Validator.\n\n```java\n@PostMapping\npublic ResponseEntity\u003cCollection\u003cUUID\u003e\u003e postStatements(\n    @RequestBody List\u003c@Valid Statement\u003e statements) {\n\n  // Process the statements\n\n  return new ResponseEntity\u003c\u003e(HttpStatus.OK);\n}\n\n```\n\n## Contributing\n\nWe welcome contributions! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for:\n- Development environment setup\n- Code style guidelines\n- Building and testing procedures\n- Pull request process\n\nFor information about creating releases, see [RELEASING.md](RELEASING.md) (maintainers only).\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fberrycloud%2Fxapi-java","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fberrycloud%2Fxapi-java","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fberrycloud%2Fxapi-java/lists"}