> ## Documentation Index
> Fetch the complete documentation index at: https://docs.xquik.com/llms.txt
> Use this file to discover all available pages before exploring further.

# X (Twitter) API Java tweet search to CSV & JSONL

> Search tweets with the Xquik Java SDK, follow cursors, and save rows as JSON Lines, CSV, or XLSX for workers, queues, and analysts in your own apps and jobs.

> For the complete documentation index, see [llms.txt](https://docs.xquik.com/llms.txt).

This guide builds on the [Java SDK setup](https://docs.xquik.com/sdks/java.md), which covers install and authentication.

## Tweet search workflow to JSON Lines, CSV, or XLSX

Export tweet search from Java services, Spring Batch, scheduled workers, or queues. Save files for data lakes, CRM enrichment, CSV, XLSX, or downstream processing.




`client.x().tweets().search` calls `GET /x/tweets/search`. Build `TweetSearchParams` with the REST query parameters `.q()`, `.limit()`, `.cursor()`, `.sinceTime()`, `.untilTime()`, and `.queryType()`.

```java theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/bash.json","/languages/csharp.json","/languages/go.json","/languages/java.json","/languages/javascript.json","/languages/kotlin.json","/languages/php.json","/languages/python.json","/languages/ruby.json","/languages/typescript.json"]}}
import com.fasterxml.jackson.databind.ObjectMapper;
import com.x_twitter_scraper.api.client.XTwitterScraperClient;
import com.x_twitter_scraper.api.client.okhttp.XTwitterScraperOkHttpClient;
import com.x_twitter_scraper.api.models.PaginatedTweets;
import com.x_twitter_scraper.api.models.SearchTweet;
import com.x_twitter_scraper.api.models.x.tweets.TweetSearchParams;
import com.x_twitter_scraper.api.models.x.tweets.TweetSearchParams.QueryType;
import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

XTwitterScraperClient client = XTwitterScraperOkHttpClient.fromEnv();
ObjectMapper objectMapper = new ObjectMapper();

String query = "from:username webhook OR SDK";
String cursor = null;
int pageIndex = 0;
List<String> headers = Arrays.asList(
    "source",
    "query",
    "tweet_id",
    "text",
    "author_id",
    "author_username",
    "author_name",
    "created_at",
    "like_count",
    "reply_count",
    "retweet_count",
    "quote_count",
    "view_count",
    "bookmark_count",
    "is_note_tweet",
    "page_index",
    "page_cursor",
    "next_cursor",
    "has_next_page"
);

try (
    BufferedWriter jsonlWriter = Files.newBufferedWriter(
        Paths.get("xquik-tweet-search.jsonl"),
        StandardCharsets.UTF_8
    );
    BufferedWriter csvWriter = Files.newBufferedWriter(
        Paths.get("xquik-tweet-search.csv"),
        StandardCharsets.UTF_8
    )
) {
    writeCsvRow(csvWriter, headers);

    do {
        String pageCursor = cursor;
        TweetSearchParams.Builder builder = TweetSearchParams.builder()
            .q(query)
            .queryType(QueryType.LATEST);

        if (cursor != null && !cursor.isEmpty()) {
            builder.cursor(cursor);
        }

        PaginatedTweets page = client.x().tweets().search(builder.build());

        for (SearchTweet tweet : page.tweets()) {
            Map<String, Object> row = new LinkedHashMap<>();
            row.put("source", "xquik.java.search");
            row.put("query", query);
            row.put("tweet_id", tweet.id());
            row.put("text", tweet.text());
            row.put("author_id", tweet.author().map(SearchTweet.Author::id).orElse(null));
            row.put("author_username", tweet.author().map(SearchTweet.Author::username).orElse(null));
            row.put("author_name", tweet.author().map(SearchTweet.Author::name).orElse(null));
            row.put("created_at", tweet.createdAt().orElse(null));
            row.put("like_count", tweet.likeCount().orElse(0L));
            row.put("reply_count", tweet.replyCount().orElse(0L));
            row.put("retweet_count", tweet.retweetCount().orElse(0L));
            row.put("quote_count", tweet.quoteCount().orElse(0L));
            row.put("view_count", tweet.viewCount().orElse(0L));
            row.put("bookmark_count", tweet.bookmarkCount().orElse(0L));
            row.put("is_note_tweet", tweet.isNoteTweet().orElse(false));
            row.put("page_index", pageIndex);
            row.put("page_cursor", pageCursor);
            row.put("next_cursor", page.hasNextPage() ? page.nextCursor() : null);
            row.put("has_next_page", page.hasNextPage());

            List<Object> csvRow = new ArrayList<>();
            for (String header : headers) {
                csvRow.add(row.get(header));
            }

            jsonlWriter.write(objectMapper.writeValueAsString(row));
            jsonlWriter.newLine();
            writeCsvRow(csvWriter, csvRow);
        }

        pageIndex++;
        cursor = page.hasNextPage() ? page.nextCursor() : null;
    } while (cursor != null && !cursor.isEmpty());
}

static void writeCsvRow(BufferedWriter writer, List<?> values) throws IOException {
    for (int index = 0; index < values.size(); index++) {
        if (index > 0) {
            writer.write(",");
        }

        Object value = values.get(index);
        String cell = value == null ? "" : String.valueOf(value);
        writer.write("\"" + cell.replace("\"", "\"\"") + "\"");
    }

    writer.newLine();
}
```

### Request mapping

> [card] .q()
>
> Java builder method `.q()` maps to REST `q`. Use it for an X (Twitter) search query. Examples are `from:username`, a keyword, hashtag, or boolean operator query.

> [card] .limit()
>
> Java builder method `.limit()` maps to REST `limit`. Use it as a 1 to 200 upper bound for a bounded pull. If `page.hasNextPage()` is true, keep the same `.q()`, filters, `.queryType()`, and `.limit()`. Do so when you continue with `page.nextCursor()`.

> [card] .cursor()
>
> Java builder method `.cursor()` maps to REST `cursor`. Pass the opaque cursor from `page.nextCursor()` to request the next page.

> [card] .sinceTime()
>
> Java builder method `.sinceTime()` maps to REST `sinceTime`. Use it as the ISO 8601 lower bound for tweet creation time.

> [card] .untilTime()
>
> Java builder method `.untilTime()` maps to REST `untilTime`. Use it as the ISO 8601 upper bound for tweet creation time.

> [card] .queryType()
>
> Java builder method `.queryType()` maps to REST `queryType`. Use `QueryType.LATEST` for chronological search or `QueryType.TOP` for engagement-ranked search.

### Returned data & handoff

`client.x().tweets().search` returns `PaginatedTweets`. Use `page.tweets()` for the tweet list. Use `page.hasNextPage()` to decide whether another page exists. Use `page.nextCursor()` as the checkpoint for the next request. For bounded pulls that return fewer tweets than the requested `.limit()`, resume from `page.nextCursor()`. To do that, pass `page.nextCursor()` back as `.cursor()`. Send the same query, filters, `QueryType`, and `.limit()` with that cursor.

Each `SearchTweet` includes generated accessors such as `id()`, `text()`, `author()`, and `createdAt()`. Others are `likeCount()`, `replyCount()`, `retweetCount()`, `quoteCount()`, `viewCount()`, and `bookmarkCount()`. It also includes `isNoteTweet()` when available. Project `page.tweets()` into JSON Lines and CSV rows with `tweet_id`, `author_username`, and engagement counts. Rows also carry `page_index`, `page_cursor`, `next_cursor`, and `has_next_page`. Workers can then resume. They can also load the same records into XLSX, CRM, warehouse, or agent workflows.

Tweet search costs 1 credit per tweet returned. Remaining credits may not cover a bounded `.limit()` request. If so, the API can return fewer tweets. If 0 paid results are affordable, it returns `402 insufficient_credits`. The SDK supports `withOptions()` for retry and request settings. For explicit `.limit()` pulls, resume with the same query, filters, `QueryType`, and `.limit()`. Only `.cursor()` changes.

## Next steps

* [Java SDK setup](https://docs.xquik.com/sdks/java.md)
* [Follower export](https://docs.xquik.com/sdks/java-follower-export.md)
* [Media tweets](https://docs.xquik.com/sdks/java-media-tweets.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.