{"id":16782573,"url":"https://github.com/davidmoten/odata-client","last_synced_at":"2025-04-09T21:18:05.574Z","repository":{"id":38314308,"uuid":"147444174","full_name":"davidmoten/odata-client","owner":"davidmoten","description":"Java client generator for a service described by OData CSDL 4.0 metadata. Includes Microsoft Graph clients (v1.0 and Beta), Graph Explorer client, Analytics for DevOps, Dynamics CRM clients","archived":false,"fork":false,"pushed_at":"2025-04-07T01:42:58.000Z","size":10231,"stargazers_count":37,"open_issues_count":10,"forks_count":9,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-04-09T21:17:53.362Z","etag":null,"topics":["azure-devops","dynamics-crm","java","microsoft-graph","microsoft-graph-sdk","odata","odata-services"],"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/davidmoten.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"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}},"created_at":"2018-09-05T01:50:53.000Z","updated_at":"2025-04-03T08:58:22.000Z","dependencies_parsed_at":"2022-08-09T02:32:19.874Z","dependency_job_id":"8e654577-4903-4899-8df4-4044c36a618e","html_url":"https://github.com/davidmoten/odata-client","commit_stats":{"total_commits":1835,"total_committers":6,"mean_commits":305.8333333333333,"dds":"0.19291553133514983","last_synced_commit":"4fd6c0fec7d27a15a2f6f31515112d6e5ab27365"},"previous_names":[],"tags_count":104,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidmoten%2Fodata-client","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidmoten%2Fodata-client/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidmoten%2Fodata-client/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidmoten%2Fodata-client/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/davidmoten","download_url":"https://codeload.github.com/davidmoten/odata-client/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248111973,"owners_count":21049578,"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":["azure-devops","dynamics-crm","java","microsoft-graph","microsoft-graph-sdk","odata","odata-services"],"created_at":"2024-10-13T07:46:04.021Z","updated_at":"2025-04-09T21:18:05.554Z","avatar_url":"https://github.com/davidmoten.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# odata-client\n\u003ca href=\"https://github.com/davidmoten/odata-client/actions/workflows/ci.yml\"\u003e\u003cimg src=\"https://github.com/davidmoten/odata-client/actions/workflows/ci.yml/badge.svg\"/\u003e\u003c/a\u003e\u003cbr/\u003e\n[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.github.davidmoten/odata-client-runtime/badge.svg?style=flat)](https://maven-badges.herokuapp.com/maven-central/com.github.davidmoten/odata-client)\u003cbr/\u003e\n[![codecov](https://codecov.io/gh/davidmoten/odata-client/branch/master/graph/badge.svg)](https://codecov.io/gh/davidmoten/odata-client)\u003cbr/\u003e\n\nGenerates java client for a service described by OData v4 metadata. Includes client libraries for Microsoft Graph primary and beta APIs, Graph Explorer API, Azure Analytics for DevOps APIs, and Microsoft Dynamics CRM. Microsoft Dynamics Finance and Operations client is in [another repo](https://github.com/davidmoten/microsoft-dynamics-finance-client).\n\nStatus : *released to Maven Central* \n\n## Features\n* High level of type safety in generated code\n* Many unit tests (real service calls are made and recorded then used for unit tests)\n* Builders and method chaining for conciseness and discoverability\n* Immutability! (generated objects from the model are immutable)\n* OData inheritance supported for serialization and derialization\n* Interschema references supported (Thompson-Reuters Datascope API uses interschema references)\n* Http calls using java.net.URLConnection or using Apache HttpClient\n* Collections are `Iterable` and streamable (via `.stream()`)\n* Paging is handled for you automatically when iterating collections\n* Bound and unbound actions and functions are supported\n* Custom requests supported via `client._custom()`\n* Generated code is very clean - well formatted, no redundant imports ([example](src/docs/FileAttachment.java))\n* Microsoft Graph v1.0 client\n* Microsoft Graph Beta client\n* Graph Explorer client (test endpoint)\n* Microsoft Analytics for DevOps 1.0, 2.0, 3.0, 4.0 and custom [clients](odata-client-microsoft-analytics/README.md).\n* Microsoft Dynamics CRM client\n* Microsoft Dynamics Finance and Operations client (see https://github.com/davidmoten/microsoft-dynamics-finance-client)\n* More generated clients can be added, just raise an issue\n* Runs on Java 8+ (including Java 11+, 17). When running \u003c11 the jaxb dependencies can be excluded from odata-client-runtime. \n\n## How to build\n`mvn clean install`\n\n## Background\nOData is an OASIS standard for building and consuming REST APIs. The *odata-client* project focuses only on OData HTTP APIs implemented using JSON payloads. A client of an OData service can be generated completely from the metadata document published by the service. An example is the Microsoft Graph Odata [metadata](odata-client-generator/src/main/odata/msgraph-metadata.xml).\n\nThe main actively supported java clients for OData 4 services are [Apache Olingo](https://github.com/apache/olingo-odata4) and the [SDL OData Framework](https://github.com/sdl/odata). However, neither of these projects generate all of the code you might need. Olingo generates some code but you still have to read the metadata xml to know what you can do with the generated classes. This project *odata-client* generates nearly all the code you need so that you just follow auto-complete on the available methods to navigate the service.\n\nMicrosoft Graph is an OData 4 service with a Java SDK being developed at https://github.com/microsoftgraph/msgraph-sdk-java. Progress is slow and steady (but happening) on that client (8 Nov 2018) and it can do a lot already. My [frustrations](https://github.com/davidmoten/odata-client/wiki/Microsoft-msgraph-sdk-java-problems-addressed-by-odata-client) with the design of that client gave rise to an investigation into generating clients for OData services in general and that investigation turned into this project.\n\n## How to generate java classes for an OData service\nAdd *odata-client-maven-plugin* and *build-helper-maven-plugin* to your `pom.xml` as per [odata-client-msgraph/pom.xml](odata-client-msgraph/pom.xml). You'll also need to save a copy of the service metadata (at http://SERVICE_ROOT/$metadata) to a file in your `src/odata` directory. Once everything is in place a build of your maven project will generate the classes and make them available as source (that's what the *build-helper-maven-plugin* does).\n\nYour plugin section will look like this:\n```xml\n\u003cplugin\u003e\n   \u003cgroupId\u003ecom.github.davidmoten\u003c/groupId\u003e\n   \u003cartifactId\u003eodata-client-maven-plugin\u003c/artifactId\u003e\n   \u003cversion\u003eVERSION_HERE\u003c/version\u003e\n   \u003cexecutions\u003e\n     \u003cexecution\u003e\n       \u003cid\u003egenerate-odata-models\u003c/id\u003e\n       \u003cphase\u003egenerate-sources\u003c/phase\u003e\n       \u003cgoals\u003e\n         \u003cgoal\u003egenerate\u003c/goal\u003e\n       \u003c/goals\u003e\n       \u003cconfiguration\u003e\n         \u003cmetadata\u003esrc/main/odata/PATH_TO_THE_METADATA_XML_FILE\u003c/metadata\u003e\n         \u003cschemas\u003e\n           \u003cschema\u003e\n            \u003cnamespace\u003eNAMESPACE_IN_YOUR_METADATA_FILE\u003c/namespace\u003e\n            \u003cpackageName\u003epackage.of.generated.classes\u003c/packageName\u003e\n           \u003c/schema\u003e\n           \u003c!-- More schemas --\u003e\n         \u003c/schemas\u003e\n       \u003c/configuration\u003e\n     \u003c/execution\u003e\n   \u003c/executions\u003e\n\u003c/plugin\u003e\n\u003cplugin\u003e\n    \u003cgroupId\u003eorg.codehaus.mojo\u003c/groupId\u003e\n    \u003cartifactId\u003ebuild-helper-maven-plugin\u003c/artifactId\u003e\n    \u003cversion\u003e3.2.0\u003c/version\u003e\n     \u003cexecutions\u003e\n      \u003cexecution\u003e\n        \u003cid\u003eadd-source\u003c/id\u003e\n         \u003cphase\u003egenerate-sources\u003c/phase\u003e\n         \u003cgoals\u003e\n           \u003cgoal\u003eadd-source\u003c/goal\u003e\n         \u003c/goals\u003e\n        \u003cconfiguration\u003e\n          \u003csources\u003e\n            \u003csource\u003e${project.build.directory}/generated-sources/java\u003c/source\u003e\n          \u003c/sources\u003e\n        \u003c/configuration\u003e\n     \u003c/execution\u003e\n    \u003c/executions\u003e\n\u003c/plugin\u003e\n```\n\n### How to create a Client for your OData Service (non-Microsoft API) with Bearer Tokens. \n\nThis example considers a custom OData api available at `$API_ENDPOINT` that authorizes using Bearer tokens in the Authorization header. The custom api has a users list that we want to return with a call to the service. The example has a hardcoded token for simplicity.\n\nThe `SchemaInfo` List should contain the `SchemaInfos` generated for your service metadata. (See generated classes in packages ending in `.schema`).\n\n```java\n    HttpClientBuilder b =\n        HttpClientBuilder\n            .create()\n            .useSystemProperties();\n            \n    BearerAuthenticator authenticator =\n        new BearerAuthenticator(\n            () -\u003e\n                \"INSERT_TOKEN_HERE\",\n            \"$API_END_POINT\");\n            \n    HttpService httpService =\n        new ApacheHttpClientHttpService( //\n            new Path(\n                \"$API_END_POINT\",\n                PathStyle.IDENTIFIERS_AS_SEGMENTS),\n            b::build,\n            authenticator::authenticate);\n            \n    final List\u003ccom.github.davidmoten.odata.client.SchemaInfo\u003e schemaInfos =\n        Arrays.asList(generate.pkg.schema.SchemaInfo.INSTANCE);\n        \n    Context context =\n        new Context(Serializer.INSTANCE, httpService, Collections.emptyMap(), schemaInfos);\n\n    // in this example Container is the name of the Container element in the metadata\n    Container client = new Container(context);\n    List\u003cString\u003e userEmails =\n        client.users()\n\t.stream()\n        .map(User::getEmail)\n        .filter(Optional::isPresent)\n        .map(x -\u003e x.get())\n        .collect(Collectors.toList());\n```\n## Limitations\n* Just one key (with multiple properties if desired) per entity is supported (secondary keys are ignored in terms of code generation). I've yet to come across a service that uses multiple keys but it is possible.\n* see [TODO](#todo)\n\n## MsGraph Client \nUse this dependency:\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003ecom.github.davidmoten\u003c/groupId\u003e\n    \u003cartifactId\u003eodata-client-msgraph\u003c/artifactId\u003e\n    \u003cversion\u003eVERSION_HERE\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\nYou can also use artifactId *odata-client-msgraph-beta* to access the beta endpoint (and they have different package names so you can use both clients in the same project if you want).\n\nIf you are running on less than Java 11 then you can exclude some dependencies but only if you are running *odata-client* version:\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003ecom.github.davidmoten\u003c/groupId\u003e\n    \u003cartifactId\u003eodata-client-msgraph\u003c/artifactId\u003e\n    \u003cversion\u003eVERSION_HERE\u003c/version\u003e\n    \u003cexclusions\u003e\n        \u003cexclusion\u003e\n            \u003cgroupId\u003eorg.glassfish.jaxb\u003c/groupId\u003e\n            \u003cartifactId\u003ejaxb-core\u003c/artifactId\u003e\n        \u003c/exclusion\u003e\n        \u003cexclusion\u003e\n            \u003cgroupId\u003eorg.glassfish.jaxb\u003c/groupId\u003e\n            \u003cartifactId\u003ejaxb-runtime\u003c/artifactId\u003e\n        \u003c/exclusion\u003e\n    \u003c/exclusions\u003e\n\u003c/dependency\u003e\n```\n\nAs slf4j is used for logging you may wish to exclude the *slf4j-api* dependency and add the slf4j logging adapter dependency (e.g. *slf4j-log4j12*) for your logging library:\n\n```xml\n\t\u003cexclusion\u003e\n\t    \u003cgroupId\u003eorg.slf4j\u003c/groupId\u003e\n\t    \u003cartifactId\u003eslf4j-api\u003c/artifactId\u003e\n\t\u003c/exclusion\u003e\n```\n\n### Create a client for the Graph Explorer\nThere's a web test page for queries called the [Graph Explorer](https://developer.microsoft.com/en-us/graph/graph-explorer). You can use the client against this service for experiments. Bear in mind that the Graph Explorer service doesn't support lots but it is still a good place to get a feel for it.\n\n```java\nGraphService client = MsGraph.explorer().build();\n```\n\nor behind a proxy:\n\n```java\nGraphService client = MsGraph\n  .explorer()\n  .proxyHost(proxyHost)\n  .proxyPort(8080)\n  .build();\n```\nHere's an example:\n\n```java\nclient \n  .users()\n  .select(\"displayName\")\n  .stream() \n  .limit(10)\n  .map(user -\u003e user.getDisplayName().orElse(\"?\"))\n  .forEach(System.out::println);\n```\n\noutput:\n\n```\nConf Room Adams\nAdele Vance\nMOD Administrator\nAlex Wilber\nAllan Deyoung\nConf Room Baker\nBen Walters\nBrian Johnson (TAILSPIN)\nChristie Cline\nConf Room Crystal\n```\n\nHere's a contrived example with Graph Explorer that gets 5 attachment names from messages and their sizes. The example makes good use of the streaming capabilities of the odata-client API:\n\n```java\nGraphService client = MsGraph.explorer().build();\nclient\n  .me()\n  .messages()\n  .select(\"id\")\n  .stream()\n  .flatMap(m -\u003e m.getAttachments().select(\"name, size\").stream())\n  .limit(5)\n  .map(a -\u003e a.getName().orElse(\"?\") + \" \" + a.getSize().orElse(-1) + \"B\")\n  .forEach(System.out::println);\n```\n\noutput:\n```\nanalytics_icon.png 2281B\nlock_circle_teal.png 2713B\ncollab_hero_left_2x.png 6496B\nProfileImage_320_48d31887-5fad-4d73-a9f5-3c356e68a038.png 335675B\ncollab_hero_right_2x.png 6611B\n```\n\n\n### Create a client for Graph v1.0\nThe first step is to create a client that will be used for all calls in your application.\n\n```java\nGraphService client = MsGraph \n    .tenantName(tenantName) \n    .clientId(clientId) \n    .clientSecret(clientSecret) \n    .connectTimeout(30, TimeUnit.SECONDS)\n    .readTimeout(60, TimeUnit.SECONDS)\n    .refreshBeforeExpiry(5, TimeUnit.MINUTES) \n    .build();\n```\n### Create a client for Graph v1.0 behind a proxy\n\n```java\nGraphService client = MsGraph \n    .tenantName(tenantName) \n    .clientId(clientId) \n    .clientSecret(clientSecret) \n    .connectTimeout(30, TimeUnit.SECONDS)\n    .readTimeout(60, TimeUnit.SECONDS)\n    .refreshBeforeExpiry(5, TimeUnit.MINUTES) \n    .proxyHost(proxyHost)\n    .proxyPort(proxyPort)\n    .proxyUsername(proxyUsername)\n    .proxyPassword(proxyPassword)\n    .build();\n```\n\n### Specify the authentication endpoint\nThis client supports Client Credential authentication only at the moment. Raise an issue if you need a different sort.\n\nTo change the authentication endpoint used to retrieve access tokens (the default is AuthenticationEndpoint.GLOBAL):\n\n```java\nGraphService client = MsGraph\n    .tenantName(tenantName)\n    .clientId(clientId)\n    .clientSecret(clientSecret)\n    .connectTimeout(30, TimeUnit.SECONDS)\n    .readTimeout(60, TimeUnit.SECONDS)\n    .refreshBeforeExpiry(5, TimeUnit.MINUTES)\n    .authenticationEndpoint(AuthenticationEndpoint.GERMANY)\n    .build();\n``` \n\nIf you want to do really complicated things with proxies or http in general you can use the `.httpClientProvider` or `.httpClientBuilderExtras` methods (coupled to Apache *HttpClient*). \n\n### Usage example 1 - simple\nHere's example usage of the *odata-client-msgraph* artifact (model classes generated from the MsGraph metadata). Let's connect to the Graph API and list all messages in the Inbox that are unread. Note that paging is completely handled for you in the `.stream()` method!\n\n```java\nString mailbox = \"me\";\nclient\n    .users(mailbox) \n    .mailFolders(\"Inbox\") \n    .messages() \n    .filter(\"isRead eq false\") \n    .expand(\"attachments\") \n    .stream() \n    .map(x -\u003e x.getSubject().orElse(\"\")) \n    .forEach(System.out::println);\n```\n### Usage example 2 - more complex\n\nHere's a more complex example which does the following:\n\n* Counts the number of messages in the *Drafts* folder\n* Prepares a new message\n* Saves the message in the *Drafts* folder\n* Changes the subject of the saved message\n* Checks the subject has changed by reloading\n* Checks the count has increased by one\n* Deletes the message\n\n```java\nMailFolderRequest drafts = client //\n    .users(mailbox) //\n    .mailFolders(\"Drafts\");\n\n// count number of messages in Drafts\nlong count = drafts.messages() //\n    .metadataNone() //\n    .stream() //\n    .count(); //\n\n// Prepare a new message\nString id = UUID.randomUUID().toString().substring(0, 6);\nMessage m = Message\n    .builderMessage() //\n    .subject(\"hi there \" + id) //\n    .body(ItemBody\n        .builder() //\n        .content(\"hello there how are you\") //\n        .contentType(BodyType.TEXT) \n        .build()) //\n    .from(Recipient\n         .builder() //\n         .emailAddress(EmailAddress\n             .builder() //\n             .address(mailbox) //\n             .build())\n         .build())\n    .build();\n\n// Create the draft message\nMessage saved = drafts.messages().post(m);\n\n// change subject\nclient //\n    .users(mailbox) //\n    .messages(saved.getId().get()) //\n    .patch(saved.withSubject(\"new subject \" + id));\n\n// check the subject has changed by reloading the message\nString amendedSubject = drafts //\n    .messages(saved.getId().get()) //\n    .get() //\n    .getSubject() //\n    .get();\nif (!(\"new subject \" + id).equals(amendedSubject)) {\n    throw new RuntimeException(\"subject not amended\");\n}\n\nlong count2 = drafts //\n    .messages() //\n    .metadataNone() //\n    .stream() //\n    .count(); //\nif (count2 != count + 1) {\n    throw new RuntimeException(\"unexpected count\");\n}\n\n// Delete the draft message\ndrafts //\n    .messages(saved.getId().get()) //\n    .delete();\n```\n### Collections and paging\n*odata-client* handles the annoying paging stuff under the covers so you don't have to worry about it. Let's look at some examples.\n\nWe'll get the list of users:\n\n```java\nList\u003cUser\u003e users = client.users().toList();\n```\nThat was easy wasn't it! Under the covers *odata-client* kept calling pages (using `@odata.nextLink`) and building the list to return. This won't always be a good idea because the Collection might be very big and you might not want all the data anyway. What are some alternatives?\n\n`client.users()` is itself an `Iterable` so you can do this:\n\n```java\nfor (User user: client.users()) \n  System.out.println(user);\n```\nor \n```java\nIterator\u003cUser\u003e it = client.users().iterator();\nwhile (it.hasNext()) \n  System.out.println(it.next());\n```\n\nYou can use `.stream()`:\n\n```java\nclient.users()\n  .stream() \n  .forEach(System.out::println);\n```\nAgain, all paging is taking care of. When the current page runs out the library requests another one.\n\nIf you do want to do explicit paging then you can do this:\n\n```java\nCollectionPage\u003cUser\u003e users = client.users().get();\n```\n`CollectionPage` has methods `currentPage` and `nextPage`.\n\n#### Your own page size\nYou can request a different page size than the default via a special HTTP request header but the server *may choose to ignore your request*:\n\n```java\nList\u003cUser\u003e users = client.users().maxPageSize(200).get().currentPage();\n```\nSo what if you want to receive the collection in custom size pages regardless of the page size being delivered by the server? *odata-client* has two utility methods to help you out. Let's chop the stream of User into pages of 15 elements:\n\n```java\nimport com.github.davidmoten.odata.client.Util;\n\nIterator\u003cList\u003cUser\u003e\u003e it = Util.buffer(client.users().iterator(), 15);\n```\nor with streams:\n```java\nStream\u003cList\u003cUser\u003e\u003e users = Util.buffer(client.users().stream(), 15);\n```\n\n#### Getting a collection from a link\nSome users need stateless paging so want to recommence the next page from the `odata.nextLink` value in the page json. Here's an example: \n\n```java\nString nextLink = \"https://...\";\nList\u003cUser\u003e users = client\n  .users()\n  .urlOverride(nextLink)\n  .get()\n  .currentPage();\n```\n\n#### Getting json for the collection\nSome users want to do a passthrough of Graph API responses in JSON format through to their clients. You can obtain a simplified collection page response as JSON like this:\n\n```java\nCollectionPage\u003cUser\u003e page = client.users().get();\nString json = page.toJsonMinimal();\n```\n\n#### Filtering a collection by type\nTo only return items of a certain type from a collection request (this is called \"restriction to instances of the derived type\" in the OData specification):\n\n```java\n\nclient\n  .directoryObjects()\n  .filter(User.class)\n  .forEach(System.out::println);\n```\n\n### Updating Microsoft Graph metadata\nDeveloper instructions:\n```bash\ncd odata-client-msgraph\n./update-metadata.sh\ncd ..\n## check if still builds!\nmvn clean install\n```\nNote that the latest metadata is downloaded from microsoft but that also:\n* `HasStream=\"true\"` is added to EntityType `itemAttachment`\n* `HasStream=\"true\"` is added to EntityType `message`\n\nURLs are:\n* https://graph.microsoft.com/v1.0/$metadata \n* https://graph.microsoft.com/beta/$metadata\n\n### Timeouts\nConnect and read timeouts can be specified when creating the client and apply globally:\n\n```java\nGraphService client = MsGraph\n    .tenantName(tenantName)\n    .clientId(clientId)\n    .clientSecret(clientSecret)\n    .connectTimeout(30, TimeUnit.SECONDS)\n    .readTimeout(60, TimeUnit.SECONDS)\n    .refreshBeforeExpiry(5, TimeUnit.MINUTES)\n    .authenticationEndpoint(AuthenticationEndpoint.GERMANY)\n    .build();\n``` \n\nGlobal timeouts can be overriden **by request**:\n\n```java\nList\u003cUser\u003e users = client\n  .users()\n  .connectTimeout(10, TimeUnit.SECONDS)\n  .readTimeout(30, Timeout.SECONDS)\n  .toList();\n```\n### Download an email in SMTP MIME format\nUse `Message.getStream()`.\n\nHere's an example using Graph Explorer where we download the MIME format of a random email returned by the service (you can run this exact code yourself to test):\n```java\nGraphService client = MsGraph.explorer().build();\nString mimeMessage = \n  client\n    .me()\n    .messages()\n    .stream()\n    .findFirst()\n    .get()\n    .getStream()\n    .get()\n    .getStringUtf8();\nSystem.out.println(mimeMessage);\n```\nOutput:\n\n```\nFrom: Sundar Ganesan \u003cadmin@m365x1515.onmicrosoft.com\u003e\nTo: Megan Bowen \u003cMeganB@M365x214355.onmicrosoft.com\u003e, Alex Wilber\nSubject:\nThread-Index: AQHWhnph+xDTvdQDAJzfEoyf/g4tCA==\nX-MS-Exchange-MessageSentRepresentingType: 1\nDate: Wed, 9 Sep 2020 07:25:22 +0000\nMessage-ID: 1599636322761\nContent-Language: en-US\nX-MS-Exchange-Organization-SupervisoryReview-TeamsContext:\n\t{\"SenderId\":\"19806f8c015344149305dc48012b4789\",\"AadGroupId\":null,\"ThreadId\":\"19:650081f4700a4414ac15cd7993129f80@thread.v2\",\"MessageId\":1599636322761,\"ParentMessageId\":null,\"TenantId\":\"91c1bcb4-2349-4abd-83c1-6ae4ffaf7f6c\",\"ThreadType\":\"chat\",\"CorrelationVector\":\"KuDTOICw9UWVpDHto11XFw.1.1.1.2441281688.1.0\"}\nX-MS-Has-Attach:\nX-MS-TNEF-Correlator:\nX-MS-Exchange-Organization-RecordReviewCfmType: 0\nContent-Type: multipart/alternative; boundary=\"_000_1599636322761_\"\nMIME-Version: 1.0\n\n--_000_1599636322761_\nContent-Type: text/plain; charset=\"us-ascii\"\n\ntest\n\n--_000_1599636322761_\nContent-Type: text/html; charset=\"us-ascii\"\n\n\u003chtml\u003e\n\u003chead\u003e\n\u003cmeta http-equiv=\"Content-Type\" content=\"text/html; charset=us-ascii\"\u003e\n\u003c/head\u003e\n\u003cbody\u003e\ntest\n\u003c/body\u003e\n\u003c/html\u003e\n\n--_000_1599636322761_--\n```\n\n### Sending an email with an attachment\n\nUnfortunately sending an email with an attachment is complicated somewhat by Microsoft Graph forcing users to use two different methods to upload an attachment depending on the size of the attachment. Default maximum size for an attachment is apparently 25MB according to Microsoft [docs](https://docs.microsoft.com/en-us/office365/servicedescriptions/exchange-online-service-description/exchange-online-limits#message-limits) though an administrator can increase the limit up to 150MB if required.\n\nTo help send an email a helper utility class called `Email` exists. Here's an example that sends an email with two attachments:\n\n```java\nEmail\n  .mailbox(mailbox) \n  .subject(\"hi there \" + new Date())\n  .bodyType(BodyType.TEXT)\n  .body(\"hello there how are you\")\n  .to(\"someone@gmail.com\")\n  .header(\"x-security-classification\", \"OFFICIAL\")\n  .attachment(file)\n  .name(\"list.txt\")\n  .contentMimeType(\"text/plain\")\n  .chunkSize(512 * 1024)\n  .attachment(\"hi there\")\n  .name(\"greeting.txt\")\n  .contentMimeType(\"text/plain\")\n  .send(client);\n```\n\nThe builder code above does quite a lot for you. For reference here's how you do it using the generated classes only (don't do this yourself, use the above Email builder):\n\n```java\nGraphService client = ...;\nFile file = ...;\nString contentType = \"text/plain\";\nString mailbox = \"me@somewhere.com\";\n\n// create new outbound mail in Drafts folder\nMailFolderRequest drafts = client //\n    .users(mailbox) //\n    .mailFolders(\"Drafts\");\nMessage m = Message.builderMessage() //\n    .subject(\"hi there \" + new Date()) //\n    .body(ItemBody.builder() //\n            .content(\"hello there how are you\") //\n            .contentType(BodyType.TEXT).build()) //\n    .from(Recipient.builder() //\n            .emailAddress(EmailAddress.builder() //\n                    .address(mailbox) //\n                    .build()) //\n            .build()) //\n    .toRecipients(Recipient.builder() //\n            .emailAddress(EmailAddress.builder() //\n                    .address(\"someone@thing.com\") //\n                    .build()) //\n            .build()) //\n    .build();\n\nm = drafts.messages().post(m);\n\n// Upload attachment to the new mail\n// We use different methods depending on the size of the attachment\n// because will fail if doesn't match the right size window\nif (file.length() \u003c 3000000) {\n    client.users(mailbox) //\n        .messages(m.getId().get()) //\n        .attachments() //\n        .post(FileAttachment.builderFileAttachment() //\n           .name(file.getName()) //\n           .contentBytes(Files.readAllBytes(file.toPath())) //\n           .contentType(contentType).build());\n} else {\n    AttachmentItem a = AttachmentItem //\n        .builder() //\n        .attachmentType(AttachmentType.FILE) //\n        .contentType(contentType) //\n        .name(file.getName()) //\n        .size(file.length()) //\n        .build();\n    \n    int chunkSize = 500*1024;\n    client //\n        .users(mailbox) //\n        .messages(m.getId().get()) //\n        .attachments() //\n        .createUploadSession(a) //\n        .get() //\n        .putChunked() //\n        .readTimeout(10, TimeUnit.MINUTES) //\n        .upload(file, chunkSize, Retries.builder().maxRetries(2).build());\n}\n\n// send the email \n// you can't just use m.send().call() because Graph doesn't \n// honour the path corresponding to m for send method and throws\n// a 405 status code\nclient\n  .users(mailbox)\n  .messages(m.getId().get())\n  .send()\n  .call();\n```\n### Delta collections\nSome functions return delta collections which track changes to resource collections. In the Graph API v1.0 there are delta functions on users, calendar events, and messages. \n\nHere's an example of usage:\n\n```java\n// get a delta that returns no users but marks where future deltas will be based on\nCollectionPage\u003cUser\u003e delta = client.users().delta().deltaTokenLatest().get();\n\n// a while later\ndelta = delta.nextDelta().get();\n\n// delta is a CollectionPage and can be iterated upon or paged through as you like\n// if you don't iterate through the collection the next call to \n// nextDelta() will do it for you because the deltaLink is on the last page \n// of the current collection \n\n// print out all users that have changed\ndelta.forEach(System.out::println);\n\n// a while later\ndelta = delta.nextDelta().get();\n...\n\n```\nA special streaming method is available also that returns a list of wrapped delta objects then the delta link to use for the next delta call:\n\n```java\nStream\u003cObjectOrDeltaLink\u003cUser\u003e\u003e delta = \n    client\n      .users()\n      .delta()\n      .get()\n      .streamWithDeltaLink();\n```\nUsing the delta link from the last element of the stream the next delta call can be made like so:\n\n```java\nStream\u003cObjectOrDeltaLink\u003cUser\u003e\u003e delta = \n    client \n      .users()\n      .overrideUrl(deltaLink)\n      .get()\n      .streamWithDeltaLink();\n```\n\n`ObjectOrDeltaLink` is serializable to JSON via its Jackson annotations and at least one user is using the `streamWithDeltaLink` method to pass large deltas over a network via WebFlux (see issue [#44](https://github.com/davidmoten/odata-client/issues/44)).\n\n## Expand\nSpecial support is provided for the `$expand` option. Here's an example:\n\n```java\nAttachment attachment = client\n  .users(emailId)\n  .mailFolders(folderId)\n  .messages(messageId)\n  .attachments(attachmentId)\n  .expand(\"microsoft.graph.itemattachment/item\")\n  .get();\nif (attachment instanceof ItemAttachment) {\n    ItemAttachment a = (ItemAttachment) attachment;\n    OutlookItem item = a.getItem().get();\n    ...\n}\n```\n\nWhen the above code is called, the line `OutlookItem item = a.getItem().get()` will not actually make a network call but rather use the json in unmapped fields of attachment. If the json is not present in the unmapped fields then a network call will be made.\n\nThis support is present for both Entity requests and Enitty Collection requests.\n\n## Contained Navigation Properties\n\nIf in the OData metadata a Navigation Property has `ContainsTarget=true` then efficiences are enabled for reading and writing such properties (as of 0.1.93). \n\nFor example, `attachments` and/or `singleValueExtendedProperties` can be defined on a `message` at creation time of the message so only one network call is required.\n\nLikewise, if `.expand(\"attachments\")` is set on retrieval of a `Message` then attachments will be included in the JSON response and the paged collection method `Message.getAttachments()` will actually use already loaded attachments rather than making an extra network call. \n\n## Logging\nThe default http client Apache *httpclient* uses *Apache Commons Logging* and the odata-client libraries use *slf4j*. To get full access to all logs you'll need to ensure that the right adapters are present that pipe logs to your preferred logging library. Tests in *odata-client-msgraph* demonstrate the use of *log4j* as the preferred logger. You'll note that these dependencies are present:\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003eorg.slf4j\u003c/groupId\u003e\n    \u003cartifactId\u003ejcl-over-slf4j\u003c/artifactId\u003e\n    \u003cversion\u003e${slf4j.version}\u003c/version\u003e\n\u003c/dependency\u003e\n\n \u003cdependency\u003e\n    \u003cgroupId\u003eorg.apache.logging.log4j\u003c/groupId\u003e\n    \u003cartifactId\u003elog4j-slf4j-impl\u003c/artifactId\u003e\n    \u003cversion\u003e${log4j.version}\u003c/version\u003e\n\u003c/dependency\u003e\n```\nA configuration file for log4j is also present (in `src/test/resources` but you might want it in `src/main/resources`):\n\n**log4j2.xml**\n```\n\u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e\n\u003cConfiguration status=\"WARN\"\u003e\n  \u003cAppenders\u003e\n    \u003cConsole name=\"Console\" target=\"SYSTEM_OUT\"\u003e\n      \u003cPatternLayout pattern=\"%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n\"/\u003e\n    \u003c/Console\u003e\n  \u003c/Appenders\u003e\n  \u003cLoggers\u003e\n    \u003cRoot level=\"INFO\"\u003e\n      \u003cAppenderRef ref=\"Console\"/\u003e\n    \u003c/Root\u003e\n  \u003c/Loggers\u003e\n\u003c/Configuration\u003e\n```\nIf you want DEBUG logging just set the Root level to DEBUG.\n\n## Serialization\n*odata-client* generated classes are annotated with Jackson JSON annotations specifically to support internal serialization and deserialization for communication with the service. Since 0.1.20 entities are annotated with `@JsonInclude(Include.NON_NULL)` so that default serialization with Jackson will exclude null values (internally this annotation is overriden for certain use cases such as when we want tell the service to update a field to null).\n\n## Custom requests\nTo get more precise control over the interaction with an OData api you can use the methods of `CustomRequest`. Here's an example that posts json to the service and recieves a json response:\n\n```java\nCustomRequest c = client._custom().withRelativeUrls();\nString json = c.getString(\"me/mailFolders/inbox/messages?$select=id\u0026count=true\", RequestOptions.EMPTY, RequestHeader.ODATA_VERSION);\nSystem.out.println(json);\n```\nOutput:\n```\n{\n\t\"@odata.context\": \"https://graph.microsoft.com/v1.0/$metadata#users('48d31887-5fad-4d73-a9f5-3c356e68a038')/mailFolders('inbox')/messages(id)\",\n\t\"@odata.count\": 21,\n\t\"@odata.nextLink\": \"https://graph.microsoft.com/v1.0/me/mailFolders/inbox/messages?$select=id\u0026count=true\u0026$skip=10\",\n\t\"value\": [\n\t\t{\n\t\t\t\"@odata.etag\": \"W/\\\"CQAAABYAAAAiIsqMbYjsT5e/T7KzowPTAAL0pwit\\\"\",\n\t\t\t\"id\": \"AAMkAGVmMDEzMTM4LTZmYWUtNDdkNC1hMDZiLTU1OGY5OTZhYmY4OABGAAAAAAAiQ8W967B7TKBjgx9rVEURBwAiIsqMbYjsT5e-T7KzowPTAAAAAAEMAAAiIsqMbYjsT5e-T7KzowPTAAL0sBcuAAA=\"\n\t\t},\n                ...\n\t]\n}\n\n```\n## Usage Notes\n### Streams\nTo find the read url for a property that is of type `Edm.Stream` you generally need to read the entity containing the stream property with the `Accept: odata.metadata=full` request header (set `.metadataFull()` before calling `get()` on an entity). As of 0.1.54 this request header is the default for Media Entities or entities with stream properties (but not for collections of these entities).\n\n## Implementation Notes\n### Generator\nSome of the metadata access patterns are O(N\u003csup\u003e2\u003c/sup\u003e) in the generator but does not appear to be significant. If you think it is then the library can start using maps for lookups.\n\n### HasStream\nSuppose Person has a Navigation Property of Photo then using the TripPin service example, calling HTTP GET of \n\nhttps://services.odata.org/V4/(S(itwk4e1fqfe4tchtlieb5rhb))/TripPinServiceRW/People('russellwhyte')/Photo\n\nreturns:\n```json\n{\n\"@odata.context\": \"http://services.odata.org/V4/(S(itwk4e1fqfe4tchtlieb5rhb))/TripPinServiceRW/$metadata#Photos/$entity\",\n\"@odata.id\": \"http://services.odata.org/V4/(S(itwk4e1fqfe4tchtlieb5rhb))/TripPinServiceRW/Photos(2)\",\n\"@odata.editLink\": \"http://services.odata.org/V4/(S(itwk4e1fqfe4tchtlieb5rhb))/TripPinServiceRW/Photos(2)\",\n\"@odata.mediaContentType\": \"image/jpeg\",\n\"@odata.mediaEtag\": \"W/\\\"08D6394B7BA10B11\\\"\",\n\"Id\": 2,\n\"Name\": \"My Photo 2\"\n}\n```\nWe then grab the `@odata.editLink` url and call that with `/$value` on the end to GET the photo bytes:\n\nhttps://services.odata.org/V4/(S(itwk4e1fqfe4tchtlieb5rhb))/TripPinServiceRW/Photos(2)/$value\n\n### Generated classes, final fields and setting via constructors\nThe initial design for setting fields was via constructors with final fields but Msgraph Beta service goes for gold on the number of fields and well exceeds the JVM limit of 256 with the `Windows10GeneralConfiguration` entity. As a consequence we have private theoretically mutable fields and a protected no-arg constructor. In practice though the fields are not mutable as the only creation access is through a `builder()` or `with*(...)` which return new instances. \n\n### PATCH support\nIf choosing the JVM Http client (via `HttpURLConnection`) then the HTTP verb `PATCH` is not supported. However, if using this client and a `ProtocolException` is thrown then the client attempts the call using the special request header `X-HTTP-Override-Method` and the verb `POST`. The detection of the `ProtocolException` is cached and future `PATCH` calls will then use the alternative methods (for the runtime life of the application).\n\n## TODO\n* support OpenType (arbitrary extra fields get written)\n* support EntityContainer inheritance (maybe, no sample that I've found uses it so far)\n* support precision, scale (maybe)\n* more decoupling of model from presentation in generation\n* use annotations (docs) in javadoc\n* support function composition\n* support `count`\n* support `raw`\n* support $ref\n* support updating/creating streams\n* support geographical primitive types (where's the spec?!!)\n* support references to other metadata files (imports)\n* \u003cstrike\u003eauto-rerequest with odata.metadata=full header if Edm.Stream is read\u003c/strike\u003e\n* support TypeDefinition\n* only generate classes that are actually used (e.g. not every Entity or ComplexType needs a corresponding Collection request)\n* remove context property \"modify.stream.edit.link\" from MsGraph client once they support their own @odata.mediaEditLink!\n* \u003cstrike\u003eadd helper methods to generated UploadUrl class in MsGraph\u003c/strike\u003e\n* \u003cstrike\u003eadd optional retry logic to StreamUploaderChunked\u003c/strike\u003e\n* increase chunked upload efficiency by writing read bytes to destination immediately, retaining the ability to retry the chunk\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdavidmoten%2Fodata-client","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdavidmoten%2Fodata-client","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdavidmoten%2Fodata-client/lists"}