[{"data":1,"prerenderedAt":3133},["ShallowReactive",2],{"/blog/modernize-java-with-cursor-and-gitlab":3,"navigation-en-us":2359,"banner-en-us":2781,"footer-en-us":2789,"blog-post-authors-en-us-Michael Friedrich":3034,"blog-related-posts-en-us-modernize-java-with-cursor-and-gitlab":3049,"blog-promotions-en-us":3071,"next-steps-en-us":3123},{"id":4,"title":5,"authors":6,"body":8,"category":2340,"date":2341,"description":2342,"extension":2343,"externalUrl":2344,"faq":2344,"featured":137,"heroImage":2345,"meta":2346,"navigation":137,"path":2347,"seo":2348,"slug":2351,"stem":2352,"tags":2353,"template":2357,"updatedDate":2344,"__hash__":2358},"blogPosts/en-us/blog/modernize-java-with-cursor-and-gitlab.md","Modernize Java with Cursor and GitLab",[7],"Michael Friedrich",{"type":9,"value":10,"toc":2317},"minimark",[11,15,18,34,37,59,62,83,226,231,283,288,291,306,347,354,361,365,376,379,385,389,392,403,410,416,419,428,432,435,441,448,454,457,463,466,469,482,486,489,498,502,511,520,558,624,635,641,644,647,651,658,664,667,670,686,693,699,702,708,711,728,731,737,744,750,753,756,762,765,782,785,791,810,820,824,827,833,840,846,849,855,870,874,877,883,886,892,896,899,903,923,937,943,949,953,958,968,976,1348,1352,1369,1565,1568,1572,1581,1590,1598,2267,2271,2274,2277,2280,2313],[12,13,14],"p",{},"\"Modernize Java 8 to Java 21\" sounds like one task. It is not. It touches the build, the runtime, dependencies, APIs, concurrency, tests, containers, and production behavior, often all at once. Ask an agent to do all of that in one prompt, and you get one enormous merge request that nobody can safely review.",[12,16,17],{},"Cursor, an AI coding agent, is good at the focused part of that problem. Give it one failing test or one bounded issue, and it can inspect the implementation, explain what went wrong, propose a fix, and run the tests without pulling us out of the development flow. What it cannot decide on its own is what \"safe\" means across a multi-step migration.",[12,19,20,21,27,28,33],{},"That is where GitLab comes in, with ",[22,23,26],"a",{"href":24,"rel":25},"https://about.gitlab.com/gitlab-duo-agent-platform/",[],"Duo Agent Platform"," orchestrating AI workflows in the rest of the software lifecycle, designed to certify the work of coding agents. The issue hierarchy with epics makes the plan durable and reviewable. The GitLab Model Context Protocol (",[22,29,32],{"href":30,"rel":31},"https://about.gitlab.com/topics/ai/model-context-protocol/",[],"MCP",") server brings that software development lifecycle context into Cursor. CI/CD, security scanning, code review, impact analysis and a cross-service test give us the evidence we need before changing production behavior.",[12,35,36],{},"In this tutorial, we will walk through three use cases with Cursor and GitLab:",[38,39,40,47,53],"ol",{},[41,42,43],"li",{},[22,44,46],{"href":45},"#fix-a-failing-end-to-end-test-with-cursor","Fix a failing Java end-to-end test with Cursor",[41,48,49],{},[22,50,52],{"href":51},"#prepare-quality-gates-for-java-21-modernization","Prepare quality gates for Java 8 to 21 modernization",[41,54,55],{},[22,56,58],{"href":57},"#modernize-http-connection-handling-with-java-21","Modernize HTTP connection handling with Java 21",[12,60,61],{},"The progression matters: Start small, add project context, and then modernize one boundary. Cursor moves fast inside that boundary. Code Review Flow, Developer Flow, CI/CD, code owner approvals, and impact analysis are what keep that speed safe. They are not optional checkpoints, but the mechanism that holds every agent-created merge request to the same standard as any other.",[12,63,64,65,70,71,76,77,82],{},"We are using the ",[22,66,69],{"href":67,"rel":68},"https://gitlab.com/gitlab-da/demo-environments/tanuki-iot-platform/sensors/java-http-metrics-collector",[],"Java HTTP metrics collector from the Tanuki IoT Platform"," for all three use cases. It checks HTTP endpoints, records metrics such as response status and timing, and sends readings to its ",[22,72,75],{"href":73,"rel":74},"https://gitlab.com/gitlab-da/demo-environments/tanuki-iot-platform/backend/rust-metrics-store",[],"Rust metrics backend",". That gives us a visible application boundary to modernize and a real backend contract to verify. A while back, we developed the Rust backend in the ",[22,78,81],{"href":79,"rel":80},"https://about.gitlab.com/blog/fix-bugs-with-codex-and-gitlab/",[],"Codex and GitLab tutorial",", and it now fits into the production architecture:",[84,85,90],"pre",{"className":86,"code":87,"language":88,"meta":89,"style":89},"language-mermaid shiki shiki-themes github-light","flowchart LR\n  subgraph sources[\"HTTP metric sources\"]\n    direction TB\n    health[\"Health endpoint\"]\n    maintenance[\"Maintenance endpoint\"]\n  end\n\n  java[\"Java HTTP metrics collector\"]\n  rust[(\"Rust metrics-store backend\")]\n\n  java -->|\"GET\"| health\n  java -->|\"GET\"| maintenance\n  java -->|\"POST /api/metrics\"| rust\n  rust -->|\"HTTP response\"| java\n\n  classDef source fill:#f8fafc,stroke:#64748b,stroke-width:2px,color:#0f172a\n  classDef focus fill:#dcfce7,stroke:#16a34a,stroke-width:3px,color:#0f172a\n  classDef backend fill:#e0f2fe,stroke:#0284c7,stroke-width:2px,color:#0f172a\n\n  class health,maintenance source\n  class java focus\n  class rust backend\n","mermaid","",[91,92,93,102,108,114,120,126,132,139,145,151,156,162,168,174,180,185,191,197,203,208,214,220],"code",{"__ignoreMap":89},[94,95,98],"span",{"class":96,"line":97},"line",1,[94,99,101],{"class":100},"sgsFI","flowchart LR\n",[94,103,105],{"class":96,"line":104},2,[94,106,107],{"class":100},"  subgraph sources[\"HTTP metric sources\"]\n",[94,109,111],{"class":96,"line":110},3,[94,112,113],{"class":100},"    direction TB\n",[94,115,117],{"class":96,"line":116},4,[94,118,119],{"class":100},"    health[\"Health endpoint\"]\n",[94,121,123],{"class":96,"line":122},5,[94,124,125],{"class":100},"    maintenance[\"Maintenance endpoint\"]\n",[94,127,129],{"class":96,"line":128},6,[94,130,131],{"class":100},"  end\n",[94,133,135],{"class":96,"line":134},7,[94,136,138],{"emptyLinePlaceholder":137},true,"\n",[94,140,142],{"class":96,"line":141},8,[94,143,144],{"class":100},"  java[\"Java HTTP metrics collector\"]\n",[94,146,148],{"class":96,"line":147},9,[94,149,150],{"class":100},"  rust[(\"Rust metrics-store backend\")]\n",[94,152,154],{"class":96,"line":153},10,[94,155,138],{"emptyLinePlaceholder":137},[94,157,159],{"class":96,"line":158},11,[94,160,161],{"class":100},"  java -->|\"GET\"| health\n",[94,163,165],{"class":96,"line":164},12,[94,166,167],{"class":100},"  java -->|\"GET\"| maintenance\n",[94,169,171],{"class":96,"line":170},13,[94,172,173],{"class":100},"  java -->|\"POST /api/metrics\"| rust\n",[94,175,177],{"class":96,"line":176},14,[94,178,179],{"class":100},"  rust -->|\"HTTP response\"| java\n",[94,181,183],{"class":96,"line":182},15,[94,184,138],{"emptyLinePlaceholder":137},[94,186,188],{"class":96,"line":187},16,[94,189,190],{"class":100},"  classDef source fill:#f8fafc,stroke:#64748b,stroke-width:2px,color:#0f172a\n",[94,192,194],{"class":96,"line":193},17,[94,195,196],{"class":100},"  classDef focus fill:#dcfce7,stroke:#16a34a,stroke-width:3px,color:#0f172a\n",[94,198,200],{"class":96,"line":199},18,[94,201,202],{"class":100},"  classDef backend fill:#e0f2fe,stroke:#0284c7,stroke-width:2px,color:#0f172a\n",[94,204,206],{"class":96,"line":205},19,[94,207,138],{"emptyLinePlaceholder":137},[94,209,211],{"class":96,"line":210},20,[94,212,213],{"class":100},"  class health,maintenance source\n",[94,215,217],{"class":96,"line":216},21,[94,218,219],{"class":100},"  class java focus\n",[94,221,223],{"class":96,"line":222},22,[94,224,225],{"class":100},"  class rust backend\n",[227,228,230],"h2",{"id":229},"prerequisites","Prerequisites",[38,232,233,241,249,252,255,264],{},[41,234,235,240],{},[22,236,239],{"href":237,"rel":238},"https://www.cursor.com/",[],"Cursor"," installed and configured. We will use the Cursor IDE in this tutorial.",[41,242,243,244,248],{},"A GitLab project with the Java collector source, issues, and modernization work items. You can use the ",[22,245,247],{"href":67,"rel":246},[],"Tanuki IoT Platform Java HTTP metrics collector",".",[41,250,251],{},"Java 8 for the first use case and Java 21 for the modernization work",[41,253,254],{},"Maven, Docker and Docker Compose for local builds and functional tests",[41,256,257,258,263],{},"The ",[22,259,262],{"href":260,"rel":261},"https://docs.gitlab.com/user/model_context_protocol/mcp_server/",[],"GitLab MCP server"," enabled on your GitLab instance or top-level group",[41,265,266,271,272,277,278,282],{},[22,267,270],{"href":268,"rel":269},"https://docs.gitlab.com/user/duo_agent_platform/flows/foundational_flows/code_review/",[],"GitLab Duo Code Review Flow",",  ",[22,273,276],{"href":274,"rel":275},"https://docs.gitlab.com/user/duo_agent_platform/flows/foundational_flows/developer/",[],"Developer Flow"," and ",[22,279,281],{"href":280},"#automate-impact-analysis-for-modernization-breaking-changes","custom flow for impact analysis on breaking changes"," enabled for the Java collector project. These flows apply project guardrails to every merge request the agent creates.",[284,285,287],"h3",{"id":286},"prepare-the-gitlab-project","Prepare the GitLab project",[12,289,290],{},"If you want to repeat the workflow in your own environment, start by importing and cloning the project and opening the project in Cursor:",[38,292,293,300,303],{},[41,294,295,296,299],{},"Import the ",[22,297,247],{"href":67,"rel":298},[]," into your GitLab environment, including all open issues.",[41,301,302],{},"Clone the project into your local environment and navigate into it.",[41,304,305],{},"Open the project in Cursor.",[84,307,311],{"className":308,"code":309,"language":310,"meta":89,"style":89},"language-shell shiki shiki-themes github-light","git clone https://gitlab.com/gitlab-da/demo-environments/tanuki-iot-platform/sensors/java-http-metrics-collector.git\ncd java-http-metrics-collector\n\ncursor .\n","shell",[91,312,313,326,335,339],{"__ignoreMap":89},[94,314,315,319,323],{"class":96,"line":97},[94,316,318],{"class":317},"s7eDp","git",[94,320,322],{"class":321},"sYBdl"," clone",[94,324,325],{"class":321}," https://gitlab.com/gitlab-da/demo-environments/tanuki-iot-platform/sensors/java-http-metrics-collector.git\n",[94,327,328,332],{"class":96,"line":104},[94,329,331],{"class":330},"sYu0t","cd",[94,333,334],{"class":321}," java-http-metrics-collector\n",[94,336,337],{"class":96,"line":110},[94,338,138],{"emptyLinePlaceholder":137},[94,340,341,344],{"class":96,"line":116},[94,342,343],{"class":317},"cursor",[94,345,346],{"class":321}," .\n",[12,348,349,350,353],{},"The project includes an ",[91,351,352],{},"AGENTS.md"," file with repository instructions and Maven commands. Cursor can use these local instructions to understand how the project is organized and how changes should be tested.",[12,355,356],{},[357,358],"img",{"alt":359,"src":360},"The Java HTTP metrics collector open in Cursor with AGENTS.md instructions","https://res.cloudinary.com/about-gitlab-com/image/upload/v1784733478/l4hqhlzxekmhobu2qg0v.png",[227,362,364],{"id":363},"fix-a-failing-end-to-end-test-with-cursor","Fix a failing end-to-end test with Cursor",[12,366,367,368,371,372,375],{},"The collector allows users to configure the HTTP status code they expect from an endpoint. The implementation, however, treats every ",[91,369,370],{},"2xx"," response as successful, and ",[91,373,374],{},"503"," errors would always fail even when configured as expected.",[12,377,378],{},"The end-to-end test already exposes this mismatch, but the CI/CD job is allowed to fail. That has turned a useful signal into accepted background noise.",[12,380,381],{},[357,382],{"alt":383,"src":384},"The failing end-to-end test and its allowed-to-fail CI/CD job","https://res.cloudinary.com/about-gitlab-com/image/upload/v1784733536/uaxqalqoceujyja7usmv.png",[284,386,388],{"id":387},"reproduce-and-fix-with-cursor","Reproduce and fix with Cursor",[12,390,391],{},"The problem can be reproduced locally. Open the Cursor IDE with a new chat, and start by describing the observable problem directly in the prompt:",[84,393,397],{"className":394,"code":395,"language":396,"meta":89,"style":89},"language-markdown shiki shiki-themes github-light","Can you help me fix the end-to-end tests in this project? Please create an analysis first, then fix it, and run the tests again.\n","markdown",[91,398,399],{"__ignoreMap":89},[94,400,401],{"class":96,"line":97},[94,402,395],{"class":100},[12,404,405,406,409],{},"Cursor starts by tracing the endpoint configuration into ",[91,407,408],{},"HttpCollector"," and the failing end-to-end test, and identifies the root cause.",[12,411,412],{},[357,413],{"alt":414,"src":415},"Cursor analysing the problem and root cause","https://res.cloudinary.com/about-gitlab-com/image/upload/v1784733605/ojz2pzcftwjfcfscjtwg.png",[12,417,418],{},"After the focused tests and the full Maven test suite pass, create a branch and merge request. The previously allowed-to-fail end-to-end job can become required when it is deterministic and green.",[84,420,422],{"className":394,"code":421,"language":396,"meta":89,"style":89},"Can you create a git branch and merge request?\n",[91,423,424],{"__ignoreMap":89},[94,425,426],{"class":96,"line":97},[94,427,421],{"class":100},[284,429,431],{"id":430},"review-and-merge","Review and merge",[12,433,434],{},"The merge request automatically triggers CI/CD build and tests, and security scanning.",[12,436,437],{},[357,438],{"alt":439,"src":440},"Merge request with fixed end-to-end tests","https://res.cloudinary.com/about-gitlab-com/image/upload/v1784733663/koz3yb3zq5ewwl6vrbgk.png",[12,442,443,447],{},[22,444,446],{"href":268,"rel":445},[],"GitLab Duo Code Review"," then reviews the focused change using the project's Java-specific review instructions.",[12,449,450],{},[357,451],{"alt":452,"src":453},"Code Review feedback","https://res.cloudinary.com/about-gitlab-com/image/upload/v1784733717/cjxeoecb4fa7yrvy7i90.png",[12,455,456],{},"When the review identifies a concrete problem, we address it through the Developer Flow before merging. That is the point: Code Review Flow ensures every agent-created merge request meets the same bar as any other, regardless of how fast Cursor produced it. The merge request remains the collaboration and decision surface.",[12,458,459],{},[357,460],{"alt":461,"src":462},"Developer Flow addressing review feedback","https://res.cloudinary.com/about-gitlab-com/image/upload/v1784733848/wehjrbkhhyhze8vtkaby.png",[12,464,465],{},"The fix gives us a behavioral baseline. We corrected a real bug without mixing it with a runtime migration, and the tests now protect the expected-status contract during the modernization work that follows.",[12,467,468],{},"Watch this video to learn how Cursor investigates and fixes the expected end-to-end tests:",[470,471,474,475],"figure",{"className":472},[473],"video_container","\n  ",[476,477,481],"iframe",{"src":478,"frameBorder":479,"allowFullScreen":480},"https://www.youtube.com/embed/vpPt8TICiZY","0","true"," ",[227,483,485],{"id":484},"prepare-quality-gates-for-java-21-modernization","Prepare quality gates for Java 21 modernization",[12,487,488],{},"The first fix worked with repository context alone. The next request is much larger: Modernize the collector from Java 8 to Java 21.",[12,490,491,492,497],{},"That work already has planning context in the ",[22,493,496],{"href":494,"rel":495},"https://gitlab.com/groups/gitlab-da/demo-environments/tanuki-iot-platform/-/work_items/13",[],"Java modernization epic",": child work items, team discussions, research with merge requests, pipeline history, dependencies, and security findings. Those details do not live in the local checkout. Instead of copying all of them into one enormous prompt, we can bring the GitLab context into Cursor with MCP.",[284,499,501],{"id":500},"configure-the-gitlab-mcp-server-in-cursor","Configure the GitLab MCP server in Cursor",[12,503,504,505,510],{},"Ensure that the GitLab MCP server is ",[22,506,509],{"href":507,"rel":508},"https://docs.gitlab.com/user/gitlab_duo/model_context_protocol/mcp_server/#prerequisites",[],"enabled"," on your instance or top-level group. Cursor uses HTTP transport to connect directly without additional dependencies.",[12,512,513,514,519],{},"To ",[22,515,518],{"href":516,"rel":517},"https://docs.gitlab.com/user/gitlab_duo/model_context_protocol/mcp_server/#connect-cursor-to-the-gitlab-mcp-server",[],"connect Cursor to the GitLab MCP server",":",[38,521,522,529,539],{},[41,523,524,525,248],{},"In Cursor, go to ",[526,527,528],"strong",{},"Settings > Cursor Settings > Tools & MCP",[41,530,531,532,535,536,248],{},"Under ",[526,533,534],{},"Installed MCP Servers",", select ",[526,537,538],{},"New MCP Server",[41,540,541,542,545,546,549,550,553,554,557],{},"Add the following definition to the ",[91,543,544],{},"mcpServers"," key in the opened ",[91,547,548],{},"mcp.json"," file. For GitLab.com, replace ",[91,551,552],{},"\u003Cgitlab.example.com>"," with ",[91,555,556],{},"gitlab.com",". For GitLab Self-Managed or Dedicated, use your GitLab instance URL.",[84,559,563],{"className":560,"code":561,"language":562,"meta":89,"style":89},"language-json shiki shiki-themes github-light","{\n  \"mcpServers\": {\n    \"GitLab\": {\n      \"type\": \"http\",\n      \"url\": \"https://\u003Cgitlab.example.com>/api/v4/mcp\"\n    }\n  }\n}\n","json",[91,564,565,570,578,585,599,609,614,619],{"__ignoreMap":89},[94,566,567],{"class":96,"line":97},[94,568,569],{"class":100},"{\n",[94,571,572,575],{"class":96,"line":104},[94,573,574],{"class":330},"  \"mcpServers\"",[94,576,577],{"class":100},": {\n",[94,579,580,583],{"class":96,"line":110},[94,581,582],{"class":330},"    \"GitLab\"",[94,584,577],{"class":100},[94,586,587,590,593,596],{"class":96,"line":116},[94,588,589],{"class":330},"      \"type\"",[94,591,592],{"class":100},": ",[94,594,595],{"class":321},"\"http\"",[94,597,598],{"class":100},",\n",[94,600,601,604,606],{"class":96,"line":122},[94,602,603],{"class":330},"      \"url\"",[94,605,592],{"class":100},[94,607,608],{"class":321},"\"https://\u003Cgitlab.example.com>/api/v4/mcp\"\n",[94,610,611],{"class":96,"line":128},[94,612,613],{"class":100},"    }\n",[94,615,616],{"class":96,"line":134},[94,617,618],{"class":100},"  }\n",[94,620,621],{"class":96,"line":141},[94,622,623],{"class":100},"}\n",[38,625,626,629,632],{"start":116},[41,627,628],{},"Save the file and wait for the OAuth authorization page to open in your browser. If it does not open, close and restart Cursor.",[41,630,631],{},"Review and approve the authorization request in your browser.",[41,633,634],{},"Return to Cursor and inspect the listed tools.",[12,636,637],{},[357,638],{"alt":639,"src":640},"The GitLab MCP server connected in Cursor after OAuth authorization","https://res.cloudinary.com/about-gitlab-com/image/upload/v1784733928/anreghjemkrnuezohhzh.png",[12,642,643],{},"You can now start a new chat and ask a question based on the available GitLab MCP tools.",[12,645,646],{},"When Cursor authenticates with GitLab MCP, it acts with your existing GitLab identity. It can only access projects and resources that you can already access. MCP brings approved context into the IDE; it does not bypass GitLab permissions.",[284,648,650],{"id":649},"prepare-the-environment-for-java-21-modernization","Prepare the environment for Java 21 modernization",[12,652,257,653,657],{},[22,654,656],{"href":494,"rel":655},[],"Java 8 to 21 modernization epic"," breaks down the necessary plan into smaller iterations, each artifact and change being testable on its own.",[12,659,660],{},[357,661],{"alt":662,"src":663},"GitLab epic with child issues","https://res.cloudinary.com/about-gitlab-com/image/upload/v1784733989/thwfzpvbs7tcm0un2xg6.png",[12,665,666],{},"The first step is to ensure that the CI/CD infrastructure tests both Java 8 and 21, in parallel. Increasing the test coverage from the beginning into each modernization task is mandatory, too.",[12,668,669],{},"Open the Cursor IDE, and use the following prompt to fetch the planning context:",[84,671,673],{"className":394,"code":672,"language":396,"meta":89,"style":89},"Please help me modernize this sensor from Java 8 to 21. We want to start with the base line for CI/CD builds in work item 14, and then also look into test coverage from 21. Start the implementation in a new Git branch called `maint-java-21` so we can continue testing different scenarios.\n",[91,674,675],{"__ignoreMap":89},[94,676,677,680,683],{"class":96,"line":97},[94,678,679],{"class":100},"Please help me modernize this sensor from Java 8 to 21. We want to start with the base line for CI/CD builds in work item 14, and then also look into test coverage from 21. Start the implementation in a new Git branch called ",[94,681,682],{"class":330},"`maint-java-21`",[94,684,685],{"class":100}," so we can continue testing different scenarios.\n",[12,687,688,689,692],{},"After finishing the work on the CI build visibility issue, Cursor can use the ",[91,690,691],{},"create_workitem_note"," MCP tool to add a summary comment into the issue, too.",[12,694,695],{},[357,696],{"alt":697,"src":698},"Cursor summary with GitLab MCP server tool call","https://res.cloudinary.com/about-gitlab-com/image/upload/v1784734071/bks4lwnncddardc05r3e.png",[12,700,701],{},"The new merge request triggers CI/CD pipelines, and also the Code Review Flow, which leaves comments about the development style guide requiring documentation.",[12,703,704],{},[357,705],{"alt":706,"src":707},"Code review feedback in the merge request","https://res.cloudinary.com/about-gitlab-com/image/upload/v1784734168/z7ootq0wqmom3yxmdexj.png",[12,709,710],{},"We can immediately address the feedback in the GitLab UI by mentioning the Developer Flow's service account.",[84,712,714],{"className":394,"code":713,"language":396,"meta":89,"style":89},"@duo-developer-\u003Cgroup-name> Can you help address the review feedback?\n",[91,715,716],{"__ignoreMap":89},[94,717,718,721,725],{"class":96,"line":97},[94,719,720],{"class":100},"@duo-developer-\u003C",[94,722,724],{"class":723},"shJU0","group-name",[94,726,727],{"class":100},"> Can you help address the review feedback?\n",[12,729,730],{},"This prompt starts a new background session, and we can focus on other tasks meanwhile. Alternatively, we can change back into the Cursor IDE and prompt its chat to address the review feedback in the merge request.",[12,732,733],{},[357,734],{"alt":735,"src":736},"Cursor IDE prompt \"There is review feedback in the MR - please help me fix it\"","https://res.cloudinary.com/about-gitlab-com/image/upload/v1784734264/n5ky8nj73bpb1imzrj6h.png",[12,738,739,740,743],{},"Cursor implements the changes, and adds comments into the merge request threads using the ",[91,741,742],{},"create_merge_request_note"," MCP tool.",[12,745,746],{},[357,747],{"alt":748,"src":749},"GitLab merge request with addressed code review comments","https://res.cloudinary.com/about-gitlab-com/image/upload/v1784734337/hah0qxe8ei5h6iefzqz4.png",[12,751,752],{},"The change keeps the temporary Java 8 baseline and adds distinct Java 21 build and test validation. This change makes compatibility visible in the pipeline before the source starts using Java 21-only APIs. Once these quality gates exist, every subsequent agent-driven change is reviewed, tested, and traceable. That is the same standard we apply to any other merge request, and it is what makes it safe to let Cursor move fast.",[12,754,755],{},"Watch this video to learn how Cursor uses GitLab MCP to prepare quality gates and address code review feedback:",[470,757,474,759],{"className":758},[473],[476,760,481],{"src":761,"frameBorder":479,"allowFullScreen":480},"https://www.youtube.com/embed/bqT2exfE5Go",[227,763,58],{"id":764},"modernize-http-connection-handling-with-java-21",[12,766,767,768,771,772,775,776,781],{},"The Java HTTP metrics collector currently uses the legacy ",[91,769,770],{},"HttpURLConnection"," API from Java 8. Java 21 modernized the HTTP library with ",[91,773,774],{},"java.net.http.HttpClient",", but this is not just a mechanical rename. Redirects, restricted headers, timeout scope, response bodies, interruption, and connection reuse can all behave differently. That is why the implementation lives in one bounded work item: ",[22,777,780],{"href":778,"rel":779},"https://gitlab.com/gitlab-da/demo-environments/tanuki-iot-platform/sensors/java-http-metrics-collector/-/work_items/16",[],"Replace HttpURLConnection with java.net.http.HttpClient",". Let's put that into practice.",[12,783,784],{},"Open the Cursor IDE with a new chat, and ask to implement the changes.",[12,786,787,790],{},[526,788,789],{},"Note:"," For this use case, we want to use a local Docker compose setup to verify the changes. If you want to reproduce the behavior, install Docker and Docker compose, otherwise remove the second prompt.",[84,792,794],{"className":394,"code":793,"language":396,"meta":89,"style":89},"We want to continue modernizing the app to Java 21 - use the same maint-java-21 branch, and start implementing issue 16.\n\nVerify the changes locally using the docker compose setup, after making the changes.\n",[91,795,796,801,805],{"__ignoreMap":89},[94,797,798],{"class":96,"line":97},[94,799,800],{"class":100},"We want to continue modernizing the app to Java 21 - use the same maint-java-21 branch, and start implementing issue 16.\n",[94,802,803],{"class":96,"line":104},[94,804,138],{"emptyLinePlaceholder":137},[94,806,807],{"class":96,"line":110},[94,808,809],{"class":100},"Verify the changes locally using the docker compose setup, after making the changes.\n",[12,811,812,813,815,816,819],{},"The GitLab MCP server provides the issue context, acceptance criteria, dependencies, and related discussions. The first code modernization issue remains deliberately synchronous and limited. It replaces ",[91,814,770],{}," with one reusable ",[91,817,818],{},"HttpClient"," and does not add virtual threads, change the time model, or remediate unrelated dependencies. Those are valuable follow-ups, but combining them would make behavior changes harder to isolate, review, and roll back.",[284,821,823],{"id":822},"implement-and-verify-locally","Implement and verify locally",[12,825,826],{},"Focused local HTTP server tests cover methods, headers, expected and unexpected status codes, redirects, timeouts, response metadata, connection failures, and interruption.",[12,828,829],{},[357,830],{"alt":831,"src":832},"Cursor running local Maven tests","https://res.cloudinary.com/about-gitlab-com/image/upload/v1784734391/fuxi3jcrcg5alhuqcrje.png",[12,834,835,836,839],{},"The Docker Compose functional test then proves that authenticated readings still arrive in the ",[91,837,838],{},"rust-metrics-store"," backend.",[12,841,842],{},[357,843],{"alt":844,"src":845},"Cursor running local Docker compose with Rust backend","https://res.cloudinary.com/about-gitlab-com/image/upload/v1784734439/w83djmi4df9dfynb9dfj.png",[12,847,848],{},"If the CI/CD pipelines are failing after the changes, leverage the GitLab MCP server tools to inspect and fix directly in the Cursor IDE without context switching.",[12,850,851],{},[357,852],{"alt":853,"src":854},"Cursor IDE with GitLab MCP server tool call to fetch CI/CD job logs","https://res.cloudinary.com/about-gitlab-com/image/upload/v1784734501/g4mfs2zdrffzb9pd8v09.png",[12,856,857,858,863,864,869],{},"In the GitLab UI, you can use ",[22,859,862],{"href":860,"rel":861},"https://docs.gitlab.com/user/duo_agent_platform/flows/foundational_flows/fix_pipeline/",[],"Fix CI/CD Pipeline Flow",", or ask the ",[22,865,868],{"href":866,"rel":867},"https://docs.gitlab.com/user/duo_agent_platform/agents/foundational_agents/ci_expert_agent/",[],"CI Expert agent"," for help.",[284,871,873],{"id":872},"cicd-and-review-evidence","CI/CD and review evidence",[12,875,876],{},"GitLab CI/CD, GitLab Duo Code Review, security scanning, and AI-assisted impact analysis provide the final evidence in the merge request. Human review still matters most at the subtle boundaries: Any intentional behavior difference should be explicit in the merge request, not discovered after deployment.",[12,878,879],{},[357,880],{"alt":881,"src":882},"Developer Flow with impact analysis","https://res.cloudinary.com/about-gitlab-com/image/upload/v1784734549/jxqds2bwzcedn7yafdvr.png",[12,884,885],{},"Watch this video to learn how Cursor and GitLab modernize the collector's HTTP library to Java 21:",[470,887,474,889],{"className":888},[473],[476,890,481],{"src":891,"frameBorder":479,"allowFullScreen":480},"https://www.youtube.com/embed/AZEDvb474n0",[227,893,895],{"id":894},"tips-for-cursor-and-gitlab","Tips for Cursor and GitLab",[12,897,898],{},"Here are some tips to use Cursor and GitLab together.",[284,900,902],{"id":901},"automate-impact-analysis-for-modernization-breaking-changes","Automate impact analysis for modernization breaking changes",[12,904,257,905,910,911,916,917,922],{},[22,906,909],{"href":907,"rel":908},"https://www.youtube.com/watch?v=AZEDvb474n0",[],"third use case"," shows the Developer Flow conducting a breaking change impact analysis. You can turn this workflow into an automated ",[22,912,915],{"href":913,"rel":914},"https://docs.gitlab.com/user/duo_agent_platform/flows/custom/",[],"custom flow"," that gets triggered when a merge request is ready, or the pipeline is OK. Additional context can be retrieved from ",[22,918,921],{"href":919,"rel":920},"https://about.gitlab.com/blog/introducing-gitlab-orbit/",[],"GitLab Orbit",", which provides a context graph across code, work items, merge requests, vulnerabilities, and more.",[12,924,925,926,931,932,248],{},"You can start inspecting the example flow in the AI Catalog: ",[22,927,930],{"href":928,"rel":929},"https://gitlab.com/explore/ai-catalog/flows/1013017/",[],"MR Impact analysis (Orbit)",". Thanks to my teammate Fatima Sarah Kalid for the inspiration. Custom flows are generally available in ",[22,933,936],{"href":934,"rel":935},"https://about.gitlab.com/blog/multi-step-software-delivery-with-agentic-flows/",[],"GitLab 19.2",[12,938,939],{},[357,940],{"alt":941,"src":942},"Custom flow in the AI Catalog","https://res.cloudinary.com/about-gitlab-com/image/upload/v1784734604/nk2faraxdhghiktndrs5.png",[12,944,945],{},[357,946],{"alt":947,"src":948},"Merge request impact analysis with a custom flow in GitLab Duo Agent Platform, and Orbit","https://res.cloudinary.com/about-gitlab-com/image/upload/v1784734740/app3pnjwqzoyulojkomq.png",[284,950,952],{"id":951},"document-guidelines-and-boundaries-for-agents","Document guidelines and boundaries for agents",[954,955,957],"h4",{"id":956},"agentsmd-for-java","AGENTS.md for Java",[12,959,960,961,967],{},"An ",[22,962,965],{"href":963,"rel":964},"https://docs.gitlab.com/user/duo_agent_platform/customize/agents_md/",[],[91,966,352],{}," file helps Cursor and other coding agents understand the project architecture, commands, code style, testing expectations, and boundaries. Keep these instructions close to the code and make them concrete enough to verify.",[12,969,970,971,248],{},"Example from the ",[22,972,975],{"href":973,"rel":974},"https://gitlab.com/gitlab-da/demo-environments/tanuki-iot-platform/sensors/java-http-metrics-collector/-/blob/main/AGENTS.md?ref_type=heads",[],"AGENTS.md in the Java HTTP metrics collector project",[84,977,979],{"className":394,"code":978,"language":396,"meta":89,"style":89},"# Java HTTP Metrics Collector - Agent Instructions\n\n## Overview\n\nThe Java HTTP Metrics Collector is a REST API metrics collection sensor for the Tanuki IoT Platform. It monitors HTTP endpoints, collects performance metrics (response time, status codes, content length), and exports them in Prometheus text format. The application runs continuously with configurable collection intervals and supports concurrent endpoint monitoring.\n\n## Code Style and Standards\n\n### Java 8 Compatibility\n\n- Do not modernize Java 8 code to Java 11+ features unless there is a GitLab issue or task specifically requesting modernization\n- Target Java 8 for source and compilation: `maven.compiler.source=1.8` and `maven.compiler.target=1.8`\n- Use Java 8 compatible patterns (e.g., anonymous inner classes instead of lambdas where appropriate)\n\n### Documentation\n\n- All public classes must have Javadoc describing purpose and usage\n- All public methods must have Javadoc with `@param` and `@return` tags\n- Include code examples in main class Javadoc\n\n### Class Organization\n\n- **Main entry point**: `HttpMetricsCollector` - orchestrates configuration loading, metric collection, and export\n- **Collector**: `HttpCollector` - performs HTTP requests and collects metrics\n- **Exporter**: `PrometheusExporter` - exports metrics in Prometheus text format\n- **Models**: `CollectorConfig`, `EndpointConfig`, `HttpMetric` - data transfer objects\n\n### Dependency Management\n\n- Always use Maven for dependency management\n- Use property-based version management for dependencies (e.g., `${jackson.version}`)\n- Keep dependencies up-to-date in `pom.xml`\n\n### Error Handling\n\n- Use try-catch blocks with proper resource management (try-with-resources where applicable)\n- Log errors using SLF4J Logger\n- Gracefully handle configuration loading failures\n- Implement proper shutdown hooks for resource cleanup\n\n### Concurrency\n\n- Use `ExecutorService` for concurrent HTTP requests\n- Thread pool size is limited to the minimum of endpoint count and 10\n- Properly shutdown executor service with timeout handling\n- Use `Future` objects to collect results from concurrent tasks\n",[91,980,981,987,991,996,1000,1005,1009,1014,1018,1023,1027,1036,1051,1058,1062,1067,1071,1078,1096,1103,1107,1112,1116,1133,1149,1165,1192,1197,1203,1208,1216,1230,1241,1246,1252,1257,1265,1273,1281,1289,1294,1300,1305,1319,1327,1335],{"__ignoreMap":89},[94,982,983],{"class":96,"line":97},[94,984,986],{"class":985},"surfw","# Java HTTP Metrics Collector - Agent Instructions\n",[94,988,989],{"class":96,"line":104},[94,990,138],{"emptyLinePlaceholder":137},[94,992,993],{"class":96,"line":110},[94,994,995],{"class":985},"## Overview\n",[94,997,998],{"class":96,"line":116},[94,999,138],{"emptyLinePlaceholder":137},[94,1001,1002],{"class":96,"line":122},[94,1003,1004],{"class":100},"The Java HTTP Metrics Collector is a REST API metrics collection sensor for the Tanuki IoT Platform. It monitors HTTP endpoints, collects performance metrics (response time, status codes, content length), and exports them in Prometheus text format. The application runs continuously with configurable collection intervals and supports concurrent endpoint monitoring.\n",[94,1006,1007],{"class":96,"line":128},[94,1008,138],{"emptyLinePlaceholder":137},[94,1010,1011],{"class":96,"line":134},[94,1012,1013],{"class":985},"## Code Style and Standards\n",[94,1015,1016],{"class":96,"line":141},[94,1017,138],{"emptyLinePlaceholder":137},[94,1019,1020],{"class":96,"line":147},[94,1021,1022],{"class":985},"### Java 8 Compatibility\n",[94,1024,1025],{"class":96,"line":153},[94,1026,138],{"emptyLinePlaceholder":137},[94,1028,1029,1033],{"class":96,"line":158},[94,1030,1032],{"class":1031},"sqxcx","-",[94,1034,1035],{"class":100}," Do not modernize Java 8 code to Java 11+ features unless there is a GitLab issue or task specifically requesting modernization\n",[94,1037,1038,1040,1043,1046,1048],{"class":96,"line":164},[94,1039,1032],{"class":1031},[94,1041,1042],{"class":100}," Target Java 8 for source and compilation: ",[94,1044,1045],{"class":330},"`maven.compiler.source=1.8`",[94,1047,277],{"class":100},[94,1049,1050],{"class":330},"`maven.compiler.target=1.8`\n",[94,1052,1053,1055],{"class":96,"line":170},[94,1054,1032],{"class":1031},[94,1056,1057],{"class":100}," Use Java 8 compatible patterns (e.g., anonymous inner classes instead of lambdas where appropriate)\n",[94,1059,1060],{"class":96,"line":176},[94,1061,138],{"emptyLinePlaceholder":137},[94,1063,1064],{"class":96,"line":182},[94,1065,1066],{"class":985},"### Documentation\n",[94,1068,1069],{"class":96,"line":187},[94,1070,138],{"emptyLinePlaceholder":137},[94,1072,1073,1075],{"class":96,"line":193},[94,1074,1032],{"class":1031},[94,1076,1077],{"class":100}," All public classes must have Javadoc describing purpose and usage\n",[94,1079,1080,1082,1085,1088,1090,1093],{"class":96,"line":199},[94,1081,1032],{"class":1031},[94,1083,1084],{"class":100}," All public methods must have Javadoc with ",[94,1086,1087],{"class":330},"`@param`",[94,1089,277],{"class":100},[94,1091,1092],{"class":330},"`@return`",[94,1094,1095],{"class":100}," tags\n",[94,1097,1098,1100],{"class":96,"line":205},[94,1099,1032],{"class":1031},[94,1101,1102],{"class":100}," Include code examples in main class Javadoc\n",[94,1104,1105],{"class":96,"line":210},[94,1106,138],{"emptyLinePlaceholder":137},[94,1108,1109],{"class":96,"line":216},[94,1110,1111],{"class":985},"### Class Organization\n",[94,1113,1114],{"class":96,"line":222},[94,1115,138],{"emptyLinePlaceholder":137},[94,1117,1119,1121,1125,1127,1130],{"class":96,"line":1118},23,[94,1120,1032],{"class":1031},[94,1122,1124],{"class":1123},"sbYKK"," **Main entry point**",[94,1126,592],{"class":100},[94,1128,1129],{"class":330},"`HttpMetricsCollector`",[94,1131,1132],{"class":100}," - orchestrates configuration loading, metric collection, and export\n",[94,1134,1136,1138,1141,1143,1146],{"class":96,"line":1135},24,[94,1137,1032],{"class":1031},[94,1139,1140],{"class":1123}," **Collector**",[94,1142,592],{"class":100},[94,1144,1145],{"class":330},"`HttpCollector`",[94,1147,1148],{"class":100}," - performs HTTP requests and collects metrics\n",[94,1150,1152,1154,1157,1159,1162],{"class":96,"line":1151},25,[94,1153,1032],{"class":1031},[94,1155,1156],{"class":1123}," **Exporter**",[94,1158,592],{"class":100},[94,1160,1161],{"class":330},"`PrometheusExporter`",[94,1163,1164],{"class":100}," - exports metrics in Prometheus text format\n",[94,1166,1168,1170,1173,1175,1178,1181,1184,1186,1189],{"class":96,"line":1167},26,[94,1169,1032],{"class":1031},[94,1171,1172],{"class":1123}," **Models**",[94,1174,592],{"class":100},[94,1176,1177],{"class":330},"`CollectorConfig`",[94,1179,1180],{"class":100},", ",[94,1182,1183],{"class":330},"`EndpointConfig`",[94,1185,1180],{"class":100},[94,1187,1188],{"class":330},"`HttpMetric`",[94,1190,1191],{"class":100}," - data transfer objects\n",[94,1193,1195],{"class":96,"line":1194},27,[94,1196,138],{"emptyLinePlaceholder":137},[94,1198,1200],{"class":96,"line":1199},28,[94,1201,1202],{"class":985},"### Dependency Management\n",[94,1204,1206],{"class":96,"line":1205},29,[94,1207,138],{"emptyLinePlaceholder":137},[94,1209,1211,1213],{"class":96,"line":1210},30,[94,1212,1032],{"class":1031},[94,1214,1215],{"class":100}," Always use Maven for dependency management\n",[94,1217,1219,1221,1224,1227],{"class":96,"line":1218},31,[94,1220,1032],{"class":1031},[94,1222,1223],{"class":100}," Use property-based version management for dependencies (e.g., ",[94,1225,1226],{"class":330},"`${jackson.version}`",[94,1228,1229],{"class":100},")\n",[94,1231,1233,1235,1238],{"class":96,"line":1232},32,[94,1234,1032],{"class":1031},[94,1236,1237],{"class":100}," Keep dependencies up-to-date in ",[94,1239,1240],{"class":330},"`pom.xml`\n",[94,1242,1244],{"class":96,"line":1243},33,[94,1245,138],{"emptyLinePlaceholder":137},[94,1247,1249],{"class":96,"line":1248},34,[94,1250,1251],{"class":985},"### Error Handling\n",[94,1253,1255],{"class":96,"line":1254},35,[94,1256,138],{"emptyLinePlaceholder":137},[94,1258,1260,1262],{"class":96,"line":1259},36,[94,1261,1032],{"class":1031},[94,1263,1264],{"class":100}," Use try-catch blocks with proper resource management (try-with-resources where applicable)\n",[94,1266,1268,1270],{"class":96,"line":1267},37,[94,1269,1032],{"class":1031},[94,1271,1272],{"class":100}," Log errors using SLF4J Logger\n",[94,1274,1276,1278],{"class":96,"line":1275},38,[94,1277,1032],{"class":1031},[94,1279,1280],{"class":100}," Gracefully handle configuration loading failures\n",[94,1282,1284,1286],{"class":96,"line":1283},39,[94,1285,1032],{"class":1031},[94,1287,1288],{"class":100}," Implement proper shutdown hooks for resource cleanup\n",[94,1290,1292],{"class":96,"line":1291},40,[94,1293,138],{"emptyLinePlaceholder":137},[94,1295,1297],{"class":96,"line":1296},41,[94,1298,1299],{"class":985},"### Concurrency\n",[94,1301,1303],{"class":96,"line":1302},42,[94,1304,138],{"emptyLinePlaceholder":137},[94,1306,1308,1310,1313,1316],{"class":96,"line":1307},43,[94,1309,1032],{"class":1031},[94,1311,1312],{"class":100}," Use ",[94,1314,1315],{"class":330},"`ExecutorService`",[94,1317,1318],{"class":100}," for concurrent HTTP requests\n",[94,1320,1322,1324],{"class":96,"line":1321},44,[94,1323,1032],{"class":1031},[94,1325,1326],{"class":100}," Thread pool size is limited to the minimum of endpoint count and 10\n",[94,1328,1330,1332],{"class":96,"line":1329},45,[94,1331,1032],{"class":1031},[94,1333,1334],{"class":100}," Properly shutdown executor service with timeout handling\n",[94,1336,1338,1340,1342,1345],{"class":96,"line":1337},46,[94,1339,1032],{"class":1031},[94,1341,1312],{"class":100},[94,1343,1344],{"class":330},"`Future`",[94,1346,1347],{"class":100}," objects to collect results from concurrent tasks\n",[954,1349,1351],{"id":1350},"code-review-instructions-for-java","Code review instructions for Java",[12,1353,1354,1355,1363,1364,519],{},"GitLab Duo Code Review Flow helps maintain style guides and boundaries. It expects specific instructions in the ",[22,1356,1359,1362],{"href":1357,"rel":1358},"https://docs.gitlab.com/user/duo_agent_platform/customize/review_instructions/",[],[91,1360,1361],{},".gitlab/duo/mr-review-instructions.yaml"," file",", for example, for ",[22,1365,1368],{"href":1366,"rel":1367},"https://gitlab.com/gitlab-da/demo-environments/tanuki-iot-platform/sensors/java-http-metrics-collector/-/blob/main/.gitlab/duo/mr-review-instructions.yaml?ref_type=heads",[],"Java",[84,1370,1374],{"className":1371,"code":1372,"language":1373,"meta":89,"style":89},"language-yaml shiki shiki-themes github-light","# Custom instructions for GitLab Duo Code Review\n\ninstructions:\n  # General guidelines\n\n  - name: Code Review\n    instructions: |\n      1. Focus on correctness and performance\n      2. Ensure code comments and documentation are clear and concise\n      3. Be respectful and constructive in comments\n\n  - name: CI/CD Configuration\n    fileFilters:\n      - \".gitlab-ci.yml\"\n    instructions: |\n      1. Do not use YAML anchors\n      2. Always use rules in jobs, avoid using `only`\n\n  # Java style guide\n\n  - name: Java Style Guide\n    fileFilters:\n      - \"**/*.java\"\n    instructions: |\n      1. Do not modernize Java 8 code to Java 11+ features, unless there is a GitLab issue or task specifically requesting modernization\n      2. All public classes must have Javadoc describing purpose and usage\n      3. All public methods must have Javadoc with @param and @return tags\n      4. Include code examples in main class Javadoc\n      5. All public methods must have at least one test case\n      6. Use httpbun.com for test endpoints (status codes, delays, JSON responses)\n","yaml",[91,1375,1376,1382,1386,1394,1399,1403,1416,1427,1432,1437,1442,1446,1457,1464,1472,1480,1485,1490,1494,1499,1503,1514,1520,1527,1535,1540,1545,1550,1555,1560],{"__ignoreMap":89},[94,1377,1378],{"class":96,"line":97},[94,1379,1381],{"class":1380},"sAwPA","# Custom instructions for GitLab Duo Code Review\n",[94,1383,1384],{"class":96,"line":104},[94,1385,138],{"emptyLinePlaceholder":137},[94,1387,1388,1391],{"class":96,"line":110},[94,1389,1390],{"class":723},"instructions",[94,1392,1393],{"class":100},":\n",[94,1395,1396],{"class":96,"line":116},[94,1397,1398],{"class":1380},"  # General guidelines\n",[94,1400,1401],{"class":96,"line":122},[94,1402,138],{"emptyLinePlaceholder":137},[94,1404,1405,1408,1411,1413],{"class":96,"line":128},[94,1406,1407],{"class":100},"  - ",[94,1409,1410],{"class":723},"name",[94,1412,592],{"class":100},[94,1414,1415],{"class":321},"Code Review\n",[94,1417,1418,1421,1423],{"class":96,"line":134},[94,1419,1420],{"class":723},"    instructions",[94,1422,592],{"class":100},[94,1424,1426],{"class":1425},"sD7c4","|\n",[94,1428,1429],{"class":96,"line":141},[94,1430,1431],{"class":321},"      1. Focus on correctness and performance\n",[94,1433,1434],{"class":96,"line":147},[94,1435,1436],{"class":321},"      2. Ensure code comments and documentation are clear and concise\n",[94,1438,1439],{"class":96,"line":153},[94,1440,1441],{"class":321},"      3. Be respectful and constructive in comments\n",[94,1443,1444],{"class":96,"line":158},[94,1445,138],{"emptyLinePlaceholder":137},[94,1447,1448,1450,1452,1454],{"class":96,"line":164},[94,1449,1407],{"class":100},[94,1451,1410],{"class":723},[94,1453,592],{"class":100},[94,1455,1456],{"class":321},"CI/CD Configuration\n",[94,1458,1459,1462],{"class":96,"line":170},[94,1460,1461],{"class":723},"    fileFilters",[94,1463,1393],{"class":100},[94,1465,1466,1469],{"class":96,"line":176},[94,1467,1468],{"class":100},"      - ",[94,1470,1471],{"class":321},"\".gitlab-ci.yml\"\n",[94,1473,1474,1476,1478],{"class":96,"line":182},[94,1475,1420],{"class":723},[94,1477,592],{"class":100},[94,1479,1426],{"class":1425},[94,1481,1482],{"class":96,"line":187},[94,1483,1484],{"class":321},"      1. Do not use YAML anchors\n",[94,1486,1487],{"class":96,"line":193},[94,1488,1489],{"class":321},"      2. Always use rules in jobs, avoid using `only`\n",[94,1491,1492],{"class":96,"line":199},[94,1493,138],{"emptyLinePlaceholder":137},[94,1495,1496],{"class":96,"line":205},[94,1497,1498],{"class":1380},"  # Java style guide\n",[94,1500,1501],{"class":96,"line":210},[94,1502,138],{"emptyLinePlaceholder":137},[94,1504,1505,1507,1509,1511],{"class":96,"line":216},[94,1506,1407],{"class":100},[94,1508,1410],{"class":723},[94,1510,592],{"class":100},[94,1512,1513],{"class":321},"Java Style Guide\n",[94,1515,1516,1518],{"class":96,"line":222},[94,1517,1461],{"class":723},[94,1519,1393],{"class":100},[94,1521,1522,1524],{"class":96,"line":1118},[94,1523,1468],{"class":100},[94,1525,1526],{"class":321},"\"**/*.java\"\n",[94,1528,1529,1531,1533],{"class":96,"line":1135},[94,1530,1420],{"class":723},[94,1532,592],{"class":100},[94,1534,1426],{"class":1425},[94,1536,1537],{"class":96,"line":1151},[94,1538,1539],{"class":321},"      1. Do not modernize Java 8 code to Java 11+ features, unless there is a GitLab issue or task specifically requesting modernization\n",[94,1541,1542],{"class":96,"line":1167},[94,1543,1544],{"class":321},"      2. All public classes must have Javadoc describing purpose and usage\n",[94,1546,1547],{"class":96,"line":1194},[94,1548,1549],{"class":321},"      3. All public methods must have Javadoc with @param and @return tags\n",[94,1551,1552],{"class":96,"line":1199},[94,1553,1554],{"class":321},"      4. Include code examples in main class Javadoc\n",[94,1556,1557],{"class":96,"line":1205},[94,1558,1559],{"class":321},"      5. All public methods must have at least one test case\n",[94,1561,1562],{"class":96,"line":1210},[94,1563,1564],{"class":321},"      6. Use httpbun.com for test endpoints (status codes, delays, JSON responses)\n",[12,1566,1567],{},"In the process of modernizing the code, the first guideline with Java 8 enforcement will need to be updated.",[284,1569,1571],{"id":1570},"turn-a-proven-workflow-into-an-agentic-skill","Turn a proven workflow into an agentic skill",[12,1573,1574,1575,1580],{},"When a specialized workflow becomes repeatable, capture it in an ",[22,1576,1579],{"href":1577,"rel":1578},"https://docs.gitlab.com/user/duo_agent_platform/customize/agent_skills/",[],"agentic skill",". Skills are loaded on demand, not populating the context window by default.",[12,1582,1583,1584,1589],{},"Start with working CI/CD, tests, and reviewed decisions so the agentic skill reflects proven practice rather than an untested plan. ",[22,1585,1588],{"href":1586,"rel":1587},"https://gitlab.com/gitlab-da/demo-environments/tanuki-iot-platform/sensors/java-http-metrics-collector/-/work_items/24",[],"Issue 24"," in the modernization epic captures the approach in a focused Java 21+ modernization agentic skill. This adds value beyond the current merge request: Future agent sessions can reuse the same safety boundaries instead of reconstructing them from earlier discussions.",[12,1591,1592,1593,519],{},"Try this example skill implementation, inspired by the existing ",[22,1594,1597],{"href":1595,"rel":1596},"https://gitlab.com/gitlab-da/demo-environments/tanuki-iot-platform/sensors/java-http-metrics-collector/-/blob/main/skills/java8-maven-maintenance/SKILL.md?ref_type=heads",[],"Java 8 Maven maintenance skill",[84,1599,1601],{"className":394,"code":1600,"language":396,"meta":89,"style":89},"---\nname: java21-modernization\ndescription: >-\n  Guide incremental Java 8 to Java 21+ modernization for the HTTP metrics\n  collector. Use when a GitLab work item asks for Java 21 CI visibility,\n  runtime/image upgrades, HttpClient migration, dependency or source API\n  modernization, or review of maint-java-21 style merge requests. Do not use\n  for routine Java 8 maintenance; prefer java8-maven-maintenance instead.\ncompatibility: Requires Maven, Docker Compose, and access to the owning GitLab work item.\n---\n\n# Java 21 Modernization\n\n## Overview\n\nModernize in small, reviewable steps. The owning work item is authoritative.\nPreserve collector → Rust metrics-store behavior unless the issue says otherwise.\n\nCompanion skill: `skills/java8-maven-maintenance/` for the Java 8 default path.\n\n## Before editing\n\n1. Read the owning issue/epic, `AGENTS.md`, `.gitlab-ci.yml`, `pom.xml`,\n   `Dockerfile`, and affected tests.\n2. Record the current baseline:\n   - `maven.compiler.source` / `target`\n   - default CI image vs any `*:java-21` jobs\n   - container base image\n   - observable CLI/Compose behavior\n3. Classify the change into **one** lane:\n   - CI visibility only\n   - runtime / image switch\n   - source / API modernization\n   - dependency upgrade\n   - tests / contract checks\n\nDo not combine lanes in one MR unless the work item explicitly requires it.\n\n## Workflow\n\nCopy and track:\n\n```text\nModernization progress:\n- [ ] Baseline recorded\n- [ ] Scoped to owning work item\n- [ ] Target-JDK CI evidence available before JDK-only APIs\n- [ ] Java 8 lane preserved until exit criteria say otherwise\n- [ ] Unit / IT / Compose checks run\n- [ ] MR documents risks, rollback, human decisions\n```\n\n## Guardrails\n\n- Do not remove Java 8 compatibility unless the work item authorizes it.\n- Do not introduce Java 21-only APIs before target-JDK CI evidence exists.\n- Do not mix runtime upgrades with unrelated refactors.\n- Do not claim performance wins without measurements.\n- Preserve the Java → Rust API and authentication contract.\n- Stop for a human decision when support policy, rollback, data format, or downstream compatibility is unclear.\n\n## Validation\n\n```bash\nmvn -Dmaven.repo.local=.m2/repository test\nmvn -Dmaven.repo.local=.m2/repository clean package\n```\n\nIf Compose or container files change:\n\n```bash\ndocker compose config --quiet\nTANUKI_INGESTION_TOKEN=replace-me docker compose up -d --build\n# confirm metric_sample logs and authenticated ingest still work\ndocker compose down -v\n```\n\n## Completion report\n\nIn the MR description, include:\n\n1. Baseline before the change\n2. Lane changed (CI / runtime / source / deps / tests)\n3. Evidence run (commands + CI jobs)\n4. Remaining risks and rollback\n5. Human decisions still open\n\n## Out of scope\n\n- Broad \"modernize everything to Java 21\" prompts\n- HTTP endpoint semantics unrelated to the JDK migration\n  (use `skills/http-endpoint-collector-behavior/`)\n- Security triage unrelated to the migration slice\n  (use `skills/security-triage-java-sensor/`)\n",[91,1602,1603,1608,1617,1627,1632,1637,1642,1647,1652,1662,1666,1670,1675,1679,1683,1687,1692,1697,1701,1712,1716,1721,1725,1748,1756,1764,1778,1791,1798,1805,1819,1826,1833,1840,1847,1854,1858,1863,1867,1872,1876,1881,1885,1890,1895,1900,1905,1911,1917,1923,1929,1935,1940,1946,1951,1959,1967,1975,1983,1991,1999,2004,2010,2015,2021,2033,2046,2051,2056,2062,2067,2072,2087,2113,2119,2132,2137,2142,2148,2153,2159,2164,2172,2180,2188,2197,2206,2211,2217,2222,2230,2238,2249,2257],{"__ignoreMap":89},[94,1604,1605],{"class":96,"line":97},[94,1606,1607],{"class":100},"---\n",[94,1609,1610,1612,1614],{"class":96,"line":104},[94,1611,1410],{"class":723},[94,1613,592],{"class":100},[94,1615,1616],{"class":321},"java21-modernization\n",[94,1618,1619,1622,1624],{"class":96,"line":110},[94,1620,1621],{"class":723},"description",[94,1623,592],{"class":100},[94,1625,1626],{"class":1425},">-\n",[94,1628,1629],{"class":96,"line":116},[94,1630,1631],{"class":321},"  Guide incremental Java 8 to Java 21+ modernization for the HTTP metrics\n",[94,1633,1634],{"class":96,"line":122},[94,1635,1636],{"class":321},"  collector. Use when a GitLab work item asks for Java 21 CI visibility,\n",[94,1638,1639],{"class":96,"line":128},[94,1640,1641],{"class":321},"  runtime/image upgrades, HttpClient migration, dependency or source API\n",[94,1643,1644],{"class":96,"line":134},[94,1645,1646],{"class":321},"  modernization, or review of maint-java-21 style merge requests. Do not use\n",[94,1648,1649],{"class":96,"line":141},[94,1650,1651],{"class":321},"  for routine Java 8 maintenance; prefer java8-maven-maintenance instead.\n",[94,1653,1654,1657,1659],{"class":96,"line":147},[94,1655,1656],{"class":723},"compatibility",[94,1658,592],{"class":100},[94,1660,1661],{"class":321},"Requires Maven, Docker Compose, and access to the owning GitLab work item.\n",[94,1663,1664],{"class":96,"line":153},[94,1665,1607],{"class":100},[94,1667,1668],{"class":96,"line":158},[94,1669,138],{"emptyLinePlaceholder":137},[94,1671,1672],{"class":96,"line":164},[94,1673,1674],{"class":985},"# Java 21 Modernization\n",[94,1676,1677],{"class":96,"line":170},[94,1678,138],{"emptyLinePlaceholder":137},[94,1680,1681],{"class":96,"line":176},[94,1682,995],{"class":985},[94,1684,1685],{"class":96,"line":182},[94,1686,138],{"emptyLinePlaceholder":137},[94,1688,1689],{"class":96,"line":187},[94,1690,1691],{"class":100},"Modernize in small, reviewable steps. The owning work item is authoritative.\n",[94,1693,1694],{"class":96,"line":193},[94,1695,1696],{"class":100},"Preserve collector → Rust metrics-store behavior unless the issue says otherwise.\n",[94,1698,1699],{"class":96,"line":199},[94,1700,138],{"emptyLinePlaceholder":137},[94,1702,1703,1706,1709],{"class":96,"line":205},[94,1704,1705],{"class":100},"Companion skill: ",[94,1707,1708],{"class":330},"`skills/java8-maven-maintenance/`",[94,1710,1711],{"class":100}," for the Java 8 default path.\n",[94,1713,1714],{"class":96,"line":210},[94,1715,138],{"emptyLinePlaceholder":137},[94,1717,1718],{"class":96,"line":216},[94,1719,1720],{"class":985},"## Before editing\n",[94,1722,1723],{"class":96,"line":222},[94,1724,138],{"emptyLinePlaceholder":137},[94,1726,1727,1730,1733,1736,1738,1741,1743,1746],{"class":96,"line":1118},[94,1728,1729],{"class":1031},"1.",[94,1731,1732],{"class":100}," Read the owning issue/epic, ",[94,1734,1735],{"class":330},"`AGENTS.md`",[94,1737,1180],{"class":100},[94,1739,1740],{"class":330},"`.gitlab-ci.yml`",[94,1742,1180],{"class":100},[94,1744,1745],{"class":330},"`pom.xml`",[94,1747,598],{"class":100},[94,1749,1750,1753],{"class":96,"line":1135},[94,1751,1752],{"class":330},"   `Dockerfile`",[94,1754,1755],{"class":100},", and affected tests.\n",[94,1757,1758,1761],{"class":96,"line":1151},[94,1759,1760],{"class":1031},"2.",[94,1762,1763],{"class":100}," Record the current baseline:\n",[94,1765,1766,1769,1772,1775],{"class":96,"line":1167},[94,1767,1768],{"class":1031},"   -",[94,1770,1771],{"class":330}," `maven.compiler.source`",[94,1773,1774],{"class":100}," / ",[94,1776,1777],{"class":330},"`target`\n",[94,1779,1780,1782,1785,1788],{"class":96,"line":1194},[94,1781,1768],{"class":1031},[94,1783,1784],{"class":100}," default CI image vs any ",[94,1786,1787],{"class":330},"`*:java-21`",[94,1789,1790],{"class":100}," jobs\n",[94,1792,1793,1795],{"class":96,"line":1199},[94,1794,1768],{"class":1031},[94,1796,1797],{"class":100}," container base image\n",[94,1799,1800,1802],{"class":96,"line":1205},[94,1801,1768],{"class":1031},[94,1803,1804],{"class":100}," observable CLI/Compose behavior\n",[94,1806,1807,1810,1813,1816],{"class":96,"line":1210},[94,1808,1809],{"class":1031},"3.",[94,1811,1812],{"class":100}," Classify the change into ",[94,1814,1815],{"class":1123},"**one**",[94,1817,1818],{"class":100}," lane:\n",[94,1820,1821,1823],{"class":96,"line":1218},[94,1822,1768],{"class":1031},[94,1824,1825],{"class":100}," CI visibility only\n",[94,1827,1828,1830],{"class":96,"line":1232},[94,1829,1768],{"class":1031},[94,1831,1832],{"class":100}," runtime / image switch\n",[94,1834,1835,1837],{"class":96,"line":1243},[94,1836,1768],{"class":1031},[94,1838,1839],{"class":100}," source / API modernization\n",[94,1841,1842,1844],{"class":96,"line":1248},[94,1843,1768],{"class":1031},[94,1845,1846],{"class":100}," dependency upgrade\n",[94,1848,1849,1851],{"class":96,"line":1254},[94,1850,1768],{"class":1031},[94,1852,1853],{"class":100}," tests / contract checks\n",[94,1855,1856],{"class":96,"line":1259},[94,1857,138],{"emptyLinePlaceholder":137},[94,1859,1860],{"class":96,"line":1267},[94,1861,1862],{"class":100},"Do not combine lanes in one MR unless the work item explicitly requires it.\n",[94,1864,1865],{"class":96,"line":1275},[94,1866,138],{"emptyLinePlaceholder":137},[94,1868,1869],{"class":96,"line":1283},[94,1870,1871],{"class":985},"## Workflow\n",[94,1873,1874],{"class":96,"line":1291},[94,1875,138],{"emptyLinePlaceholder":137},[94,1877,1878],{"class":96,"line":1296},[94,1879,1880],{"class":100},"Copy and track:\n",[94,1882,1883],{"class":96,"line":1302},[94,1884,138],{"emptyLinePlaceholder":137},[94,1886,1887],{"class":96,"line":1307},[94,1888,1889],{"class":100},"```text\n",[94,1891,1892],{"class":96,"line":1321},[94,1893,1894],{"class":100},"Modernization progress:\n",[94,1896,1897],{"class":96,"line":1329},[94,1898,1899],{"class":100},"- [ ] Baseline recorded\n",[94,1901,1902],{"class":96,"line":1337},[94,1903,1904],{"class":100},"- [ ] Scoped to owning work item\n",[94,1906,1908],{"class":96,"line":1907},47,[94,1909,1910],{"class":100},"- [ ] Target-JDK CI evidence available before JDK-only APIs\n",[94,1912,1914],{"class":96,"line":1913},48,[94,1915,1916],{"class":100},"- [ ] Java 8 lane preserved until exit criteria say otherwise\n",[94,1918,1920],{"class":96,"line":1919},49,[94,1921,1922],{"class":100},"- [ ] Unit / IT / Compose checks run\n",[94,1924,1926],{"class":96,"line":1925},50,[94,1927,1928],{"class":100},"- [ ] MR documents risks, rollback, human decisions\n",[94,1930,1932],{"class":96,"line":1931},51,[94,1933,1934],{"class":100},"```\n",[94,1936,1938],{"class":96,"line":1937},52,[94,1939,138],{"emptyLinePlaceholder":137},[94,1941,1943],{"class":96,"line":1942},53,[94,1944,1945],{"class":985},"## Guardrails\n",[94,1947,1949],{"class":96,"line":1948},54,[94,1950,138],{"emptyLinePlaceholder":137},[94,1952,1954,1956],{"class":96,"line":1953},55,[94,1955,1032],{"class":1031},[94,1957,1958],{"class":100}," Do not remove Java 8 compatibility unless the work item authorizes it.\n",[94,1960,1962,1964],{"class":96,"line":1961},56,[94,1963,1032],{"class":1031},[94,1965,1966],{"class":100}," Do not introduce Java 21-only APIs before target-JDK CI evidence exists.\n",[94,1968,1970,1972],{"class":96,"line":1969},57,[94,1971,1032],{"class":1031},[94,1973,1974],{"class":100}," Do not mix runtime upgrades with unrelated refactors.\n",[94,1976,1978,1980],{"class":96,"line":1977},58,[94,1979,1032],{"class":1031},[94,1981,1982],{"class":100}," Do not claim performance wins without measurements.\n",[94,1984,1986,1988],{"class":96,"line":1985},59,[94,1987,1032],{"class":1031},[94,1989,1990],{"class":100}," Preserve the Java → Rust API and authentication contract.\n",[94,1992,1994,1996],{"class":96,"line":1993},60,[94,1995,1032],{"class":1031},[94,1997,1998],{"class":100}," Stop for a human decision when support policy, rollback, data format, or downstream compatibility is unclear.\n",[94,2000,2002],{"class":96,"line":2001},61,[94,2003,138],{"emptyLinePlaceholder":137},[94,2005,2007],{"class":96,"line":2006},62,[94,2008,2009],{"class":985},"## Validation\n",[94,2011,2013],{"class":96,"line":2012},63,[94,2014,138],{"emptyLinePlaceholder":137},[94,2016,2018],{"class":96,"line":2017},64,[94,2019,2020],{"class":100},"```bash\n",[94,2022,2024,2027,2030],{"class":96,"line":2023},65,[94,2025,2026],{"class":317},"mvn",[94,2028,2029],{"class":330}," -Dmaven.repo.local=.m2/repository",[94,2031,2032],{"class":321}," test\n",[94,2034,2036,2038,2040,2043],{"class":96,"line":2035},66,[94,2037,2026],{"class":317},[94,2039,2029],{"class":330},[94,2041,2042],{"class":321}," clean",[94,2044,2045],{"class":321}," package\n",[94,2047,2049],{"class":96,"line":2048},67,[94,2050,1934],{"class":100},[94,2052,2054],{"class":96,"line":2053},68,[94,2055,138],{"emptyLinePlaceholder":137},[94,2057,2059],{"class":96,"line":2058},69,[94,2060,2061],{"class":100},"If Compose or container files change:\n",[94,2063,2065],{"class":96,"line":2064},70,[94,2066,138],{"emptyLinePlaceholder":137},[94,2068,2070],{"class":96,"line":2069},71,[94,2071,2020],{"class":100},[94,2073,2075,2078,2081,2084],{"class":96,"line":2074},72,[94,2076,2077],{"class":317},"docker",[94,2079,2080],{"class":321}," compose",[94,2082,2083],{"class":321}," config",[94,2085,2086],{"class":330}," --quiet\n",[94,2088,2090,2093,2096,2099,2102,2104,2107,2110],{"class":96,"line":2089},73,[94,2091,2092],{"class":100},"TANUKI_INGESTION_TOKEN",[94,2094,2095],{"class":1425},"=",[94,2097,2098],{"class":321},"replace-me",[94,2100,2101],{"class":317}," docker",[94,2103,2080],{"class":321},[94,2105,2106],{"class":321}," up",[94,2108,2109],{"class":330}," -d",[94,2111,2112],{"class":330}," --build\n",[94,2114,2116],{"class":96,"line":2115},74,[94,2117,2118],{"class":1380},"# confirm metric_sample logs and authenticated ingest still work\n",[94,2120,2122,2124,2126,2129],{"class":96,"line":2121},75,[94,2123,2077],{"class":317},[94,2125,2080],{"class":321},[94,2127,2128],{"class":321}," down",[94,2130,2131],{"class":330}," -v\n",[94,2133,2135],{"class":96,"line":2134},76,[94,2136,1934],{"class":100},[94,2138,2140],{"class":96,"line":2139},77,[94,2141,138],{"emptyLinePlaceholder":137},[94,2143,2145],{"class":96,"line":2144},78,[94,2146,2147],{"class":985},"## Completion report\n",[94,2149,2151],{"class":96,"line":2150},79,[94,2152,138],{"emptyLinePlaceholder":137},[94,2154,2156],{"class":96,"line":2155},80,[94,2157,2158],{"class":100},"In the MR description, include:\n",[94,2160,2162],{"class":96,"line":2161},81,[94,2163,138],{"emptyLinePlaceholder":137},[94,2165,2167,2169],{"class":96,"line":2166},82,[94,2168,1729],{"class":1031},[94,2170,2171],{"class":100}," Baseline before the change\n",[94,2173,2175,2177],{"class":96,"line":2174},83,[94,2176,1760],{"class":1031},[94,2178,2179],{"class":100}," Lane changed (CI / runtime / source / deps / tests)\n",[94,2181,2183,2185],{"class":96,"line":2182},84,[94,2184,1809],{"class":1031},[94,2186,2187],{"class":100}," Evidence run (commands + CI jobs)\n",[94,2189,2191,2194],{"class":96,"line":2190},85,[94,2192,2193],{"class":1031},"4.",[94,2195,2196],{"class":100}," Remaining risks and rollback\n",[94,2198,2200,2203],{"class":96,"line":2199},86,[94,2201,2202],{"class":1031},"5.",[94,2204,2205],{"class":100}," Human decisions still open\n",[94,2207,2209],{"class":96,"line":2208},87,[94,2210,138],{"emptyLinePlaceholder":137},[94,2212,2214],{"class":96,"line":2213},88,[94,2215,2216],{"class":985},"## Out of scope\n",[94,2218,2220],{"class":96,"line":2219},89,[94,2221,138],{"emptyLinePlaceholder":137},[94,2223,2225,2227],{"class":96,"line":2224},90,[94,2226,1032],{"class":1031},[94,2228,2229],{"class":100}," Broad \"modernize everything to Java 21\" prompts\n",[94,2231,2233,2235],{"class":96,"line":2232},91,[94,2234,1032],{"class":1031},[94,2236,2237],{"class":100}," HTTP endpoint semantics unrelated to the JDK migration\n",[94,2239,2241,2244,2247],{"class":96,"line":2240},92,[94,2242,2243],{"class":100},"  (use ",[94,2245,2246],{"class":330},"`skills/http-endpoint-collector-behavior/`",[94,2248,1229],{"class":100},[94,2250,2252,2254],{"class":96,"line":2251},93,[94,2253,1032],{"class":1031},[94,2255,2256],{"class":100}," Security triage unrelated to the migration slice\n",[94,2258,2260,2262,2265],{"class":96,"line":2259},94,[94,2261,2243],{"class":100},[94,2263,2264],{"class":330},"`skills/security-triage-java-sensor/`",[94,2266,1229],{"class":100},[227,2268,2270],{"id":2269},"summary","Summary",[12,2272,2273],{},"The three use cases in this tutorial build on each other. Cursor fixed an accepted end-to-end test failure using repository context alone. Then the GitLab MCP server brought in the modernization plan, so Cursor could put quality gates in place and close the loop on Duo review feedback directly from the IDE. Finally, Cursor made one bounded Java 21 change, replacing HttpURLConnection with a reusable HttpClient, backed by focused tests, cross-service ingestion runs, pipeline, security scans, software bill of materials, review evidence, and impact analysis.",[12,2275,2276],{},"A legacy Java 8 codebase does not get safer to modernize just because an agent is writing the code. It gets safer because every change is scoped, reviewed, tested against Java 8 and Java 21, and traceable back to a work item with the decisions and evidence behind it. Cursor handles the implementation. GitLab handles the proof. Together, they make the migration something a team can trust.",[12,2278,2279],{},"If you want to try this workflow, start with one test that exposes an accepted failure in a legacy application. Make that test reliable, capture the larger modernization plan in GitLab, and choose one boundary you can change and prove independently. That gives the agent a focused task and gives the team evidence they can review.",[2281,2282,2283,2290,2298],"blockquote",{},[12,2284,2285,2286,248],{},"If you are not using GitLab Duo Agent Platform today, you can start with ",[22,2287,2289],{"href":24,"rel":2288},[],"a free trial",[12,2291,2292,2293,248],{},"If you are already using GitLab in the free tier, you can sign up for GitLab Duo Agent Platform by ",[22,2294,2297],{"href":2295,"rel":2296},"https://docs.gitlab.com/subscriptions/gitlab_credits/#for-the-free-tier-on-gitlabcom",[],"following a few simple steps",[12,2299,2300,2301,2306,2307,2312],{},"And if you are an existing subscriber to GitLab Premium or Ultimate, you can get started simply by ",[22,2302,2305],{"href":2303,"rel":2304},"https://docs.gitlab.com/user/duo_agent_platform/turn_on_off/",[],"turning on Duo Agent Platform"," and start using the GitLab Credits ",[22,2308,2311],{"href":2309,"rel":2310},"https://docs.gitlab.com/subscriptions/gitlab_credits/#included-credits",[],"that are included"," with your subscription.",[2314,2315,2316],"style",{},"html pre.shiki code .sgsFI, html code.shiki .sgsFI{--shiki-default:#24292E}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s7eDp, html code.shiki .s7eDp{--shiki-default:#6F42C1}html pre.shiki code .sYBdl, html code.shiki .sYBdl{--shiki-default:#032F62}html pre.shiki code .sYu0t, html code.shiki .sYu0t{--shiki-default:#005CC5}html pre.shiki code .shJU0, html code.shiki .shJU0{--shiki-default:#22863A}html pre.shiki code .surfw, html code.shiki .surfw{--shiki-default:#005CC5;--shiki-default-font-weight:bold}html pre.shiki code .sqxcx, html code.shiki .sqxcx{--shiki-default:#E36209}html pre.shiki code .sbYKK, html code.shiki .sbYKK{--shiki-default:#24292E;--shiki-default-font-weight:bold}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .sD7c4, html code.shiki .sD7c4{--shiki-default:#D73A49}",{"title":89,"searchDepth":104,"depth":104,"links":2318},[2319,2322,2326,2330,2334,2339],{"id":229,"depth":104,"text":230,"children":2320},[2321],{"id":286,"depth":110,"text":287},{"id":363,"depth":104,"text":364,"children":2323},[2324,2325],{"id":387,"depth":110,"text":388},{"id":430,"depth":110,"text":431},{"id":484,"depth":104,"text":485,"children":2327},[2328,2329],{"id":500,"depth":110,"text":501},{"id":649,"depth":110,"text":650},{"id":764,"depth":104,"text":58,"children":2331},[2332,2333],{"id":822,"depth":110,"text":823},{"id":872,"depth":110,"text":873},{"id":894,"depth":104,"text":895,"children":2335},[2336,2337,2338],{"id":901,"depth":110,"text":902},{"id":951,"depth":110,"text":952},{"id":1570,"depth":110,"text":1571},{"id":2269,"depth":104,"text":2270},"ai","2026-07-22","This tutorial starts with a focused tests fix, introduces quality gates, and implements code changes with development lifecycle context, tests, and review evidence.","md",null,"https://res.cloudinary.com/about-gitlab-com/image/upload/v1776457632/llddiylsgwuze0u1rjks.png",{},"/en-us/blog/modernize-java-with-cursor-and-gitlab",{"config":2349,"title":5,"description":2342},{"noIndex":2350},false,"modernize-java-with-cursor-and-gitlab","en-us/blog/modernize-java-with-cursor-and-gitlab",[2354,2355,2356],"AI","product","tutorial","BlogPost","z4ERoIN7A9nvaiqRZQconFwyKZ1s8-5IVYyzKgssek0",{"logo":2360,"freeTrial":2365,"sales":2370,"login":2375,"items":2380,"search":2702,"minimal":2732,"duo":2751,"switchNav":2760,"pricingDeployment":2771},{"config":2361},{"href":2362,"dataGaName":2363,"dataGaLocation":2364},"/","gitlab logo","header",{"text":2366,"config":2367},"Get free trial",{"href":2368,"dataGaName":2369,"dataGaLocation":2364},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com&glm_content=default-saas-trial/","free trial",{"text":2371,"config":2372},"Request a demo",{"href":2373,"dataGaName":2374,"dataGaLocation":2364},"/sales/?contact-topic=request-demo","sales",{"text":2376,"config":2377},"Sign in",{"href":2378,"dataGaName":2379,"dataGaLocation":2364},"https://gitlab.com/users/sign_in/","sign in",[2381,2410,2510,2515,2624,2680],{"text":2382,"config":2383,"menu":2385},"Platform",{"dataNavLevelOne":2384},"platform",{"type":2386,"columns":2387},"cards",[2388,2394,2402],{"title":2382,"description":2389,"link":2390},"The intelligent orchestration platform for DevSecOps",{"text":2391,"config":2392},"Explore our Platform",{"href":2393,"dataGaName":2384,"dataGaLocation":2364},"/platform/",{"title":2395,"description":2396,"link":2397},"GitLab Duo Agent Platform","Agentic AI for the entire software lifecycle",{"text":2398,"config":2399},"Meet GitLab Duo",{"href":2400,"dataGaName":2401,"dataGaLocation":2364},"/gitlab-duo-agent-platform/","gitlab duo agent platform",{"title":2403,"description":2404,"link":2405},"Why GitLab","See the top reasons enterprises choose GitLab",{"text":2406,"config":2407},"Learn more",{"href":2408,"dataGaName":2409,"dataGaLocation":2364},"/why-gitlab/","why gitlab",{"text":2411,"left":137,"config":2412,"menu":2414},"Product",{"dataNavLevelOne":2413},"solutions",{"type":2415,"link":2416,"columns":2420,"feature":2489},"lists",{"text":2417,"config":2418},"View all Solutions",{"href":2419,"dataGaName":2413,"dataGaLocation":2364},"/solutions/",[2421,2445,2468],{"title":2422,"description":2423,"link":2424,"items":2429},"Automation","CI/CD and automation to accelerate deployment",{"config":2425},{"icon":2426,"href":2427,"dataGaName":2428,"dataGaLocation":2364},"AutomatedCodeAlt","/solutions/delivery-automation/","automated software delivery",[2430,2434,2437,2441],{"text":2431,"config":2432},"CI/CD",{"href":2433,"dataGaLocation":2364,"dataGaName":2431},"/solutions/continuous-integration/",{"text":2395,"config":2435},{"href":2400,"dataGaLocation":2364,"dataGaName":2436},"gitlab duo agent platform - product menu",{"text":2438,"config":2439},"Source Code Management",{"href":2440,"dataGaLocation":2364,"dataGaName":2438},"/solutions/source-code-management/",{"text":2442,"config":2443},"Automated Software Delivery",{"href":2427,"dataGaLocation":2364,"dataGaName":2444},"Automated software delivery",{"title":2446,"description":2447,"link":2448,"items":2453},"Security","Deliver code faster without compromising security",{"config":2449},{"href":2450,"dataGaName":2451,"dataGaLocation":2364,"icon":2452},"/solutions/application-security-testing/","security and compliance","ShieldCheckLight",[2454,2458,2463],{"text":2455,"config":2456},"Application Security Testing",{"href":2450,"dataGaName":2457,"dataGaLocation":2364},"Application security testing",{"text":2459,"config":2460},"Software Supply Chain Security",{"href":2461,"dataGaLocation":2364,"dataGaName":2462},"/solutions/supply-chain/","Software supply chain security",{"text":2464,"config":2465},"Software Compliance",{"href":2466,"dataGaName":2467,"dataGaLocation":2364},"/solutions/software-compliance/","software compliance",{"title":2469,"link":2470,"items":2475},"Measurement",{"config":2471},{"icon":2472,"href":2473,"dataGaName":2474,"dataGaLocation":2364},"DigitalTransformation","/solutions/visibility-measurement/","visibility and measurement",[2476,2480,2484],{"text":2477,"config":2478},"Visibility & Measurement",{"href":2473,"dataGaLocation":2364,"dataGaName":2479},"Visibility and Measurement",{"text":2481,"config":2482},"Value Stream Management",{"href":2483,"dataGaLocation":2364,"dataGaName":2481},"/solutions/value-stream-management/",{"text":2485,"config":2486},"Analytics & Insights",{"href":2487,"dataGaLocation":2364,"dataGaName":2488},"/solutions/analytics-and-insights/","Analytics and insights",{"title":2490,"type":2415,"items":2491},"GitLab for",[2492,2498,2504],{"text":2493,"config":2494},"Enterprise",{"icon":2495,"href":2496,"dataGaLocation":2364,"dataGaName":2497},"Building","/enterprise/","enterprise",{"text":2499,"config":2500},"Small Business",{"icon":2501,"href":2502,"dataGaLocation":2364,"dataGaName":2503},"Work","/small-business/","small business",{"text":2505,"config":2506},"Public Sector",{"icon":2507,"href":2508,"dataGaLocation":2364,"dataGaName":2509},"Organization","/solutions/public-sector/","public sector",{"text":2511,"config":2512},"Pricing",{"href":2513,"dataGaName":2514,"dataGaLocation":2364,"dataNavLevelOne":2514},"/pricing/","pricing",{"text":2516,"config":2517,"menu":2519},"Resources",{"dataNavLevelOne":2518},"resources",{"type":2415,"link":2520,"columns":2524,"feature":2613},{"text":2521,"config":2522},"View all resources",{"href":2523,"dataGaName":2518,"dataGaLocation":2364},"/resources/",[2525,2558,2580],{"title":2526,"items":2527},"Getting started",[2528,2533,2538,2543,2548,2553],{"text":2529,"config":2530},"Install",{"href":2531,"dataGaName":2532,"dataGaLocation":2364},"/install/","install",{"text":2534,"config":2535},"Quick start guides",{"href":2536,"dataGaName":2537,"dataGaLocation":2364},"/get-started/","quick setup checklists",{"text":2539,"config":2540},"Learn",{"href":2541,"dataGaLocation":2364,"dataGaName":2542},"https://university.gitlab.com/","learn",{"text":2544,"config":2545},"Product documentation",{"href":2546,"dataGaName":2547,"dataGaLocation":2364},"https://docs.gitlab.com/","product documentation",{"text":2549,"config":2550},"Best practice videos",{"href":2551,"dataGaName":2552,"dataGaLocation":2364},"/getting-started-videos/","best practice videos",{"text":2554,"config":2555},"Integrations",{"href":2556,"dataGaName":2557,"dataGaLocation":2364},"/integrations/","integrations",{"title":2559,"items":2560},"Discover",[2561,2566,2571,2575],{"text":2562,"config":2563},"Customer success stories",{"href":2564,"dataGaName":2565,"dataGaLocation":2364},"/customers/","customer success stories",{"text":2567,"config":2568},"Blog",{"href":2569,"dataGaName":2570,"dataGaLocation":2364},"/blog/","blog",{"text":2572,"config":2573},"The Source",{"href":2574,"dataGaName":2570,"dataGaLocation":2364},"/the-source/",{"text":2576,"config":2577},"Remote",{"href":2578,"dataGaName":2579,"dataGaLocation":2364},"https://handbook.gitlab.com/handbook/company/culture/all-remote/","remote",{"title":2581,"items":2582},"Connect",[2583,2588,2593,2598,2603,2608],{"text":2584,"config":2585},"GitLab Services",{"href":2586,"dataGaName":2587,"dataGaLocation":2364},"/services/","services",{"text":2589,"config":2590},"Contribute",{"href":2591,"dataGaName":2592,"dataGaLocation":2364},"https://contributors.gitlab.com","contribute",{"text":2594,"config":2595},"Community",{"href":2596,"dataGaName":2597,"dataGaLocation":2364},"/community/","community",{"text":2599,"config":2600},"Forum",{"href":2601,"dataGaName":2602,"dataGaLocation":2364},"https://forum.gitlab.com/","forum",{"text":2604,"config":2605},"Events",{"href":2606,"dataGaName":2607,"dataGaLocation":2364},"/events/","events",{"text":2609,"config":2610},"Partners",{"href":2611,"dataGaName":2612,"dataGaLocation":2364},"/partners/","partners",{"config":2614,"title":2617,"text":2618,"link":2619},{"background":2615,"textColor":2616},"url('https://res.cloudinary.com/about-gitlab-com/image/upload/v1777322348/qpq8yrgn8knii57omj0c.png')","#000","What’s new in GitLab","Stay updated with our latest features and improvements.",{"text":2620,"config":2621},"Read the latest",{"href":2622,"dataGaName":2623,"dataGaLocation":2364},"/whats-new/","whats new",{"text":2625,"config":2626,"menu":2628},"Company",{"dataNavLevelOne":2627},"company",{"type":2415,"columns":2629},[2630],{"items":2631},[2632,2637,2643,2645,2650,2655,2660,2665,2670,2675],{"text":2633,"config":2634},"About",{"href":2635,"dataGaName":2636,"dataGaLocation":2364},"/company/","about",{"text":2638,"config":2639,"footerGa":2642},"Jobs",{"href":2640,"dataGaName":2641,"dataGaLocation":2364},"/jobs/","jobs",{"dataGaName":2641},{"text":2604,"config":2644},{"href":2606,"dataGaName":2607,"dataGaLocation":2364},{"text":2646,"config":2647},"Leadership",{"href":2648,"dataGaName":2649,"dataGaLocation":2364},"/company/team/e-group/","leadership",{"text":2651,"config":2652},"Handbook",{"href":2653,"dataGaName":2654,"dataGaLocation":2364},"https://handbook.gitlab.com/","handbook",{"text":2656,"config":2657},"Investor relations",{"href":2658,"dataGaName":2659,"dataGaLocation":2364},"https://ir.gitlab.com/overview/default.aspx","investor relations",{"text":2661,"config":2662},"Trust Center",{"href":2663,"dataGaName":2664,"dataGaLocation":2364},"/security/","trust center",{"text":2666,"config":2667},"AI Transparency Center",{"href":2668,"dataGaName":2669,"dataGaLocation":2364},"/ai-transparency-center/","ai transparency center",{"text":2671,"config":2672},"Newsletter",{"href":2673,"dataGaName":2674,"dataGaLocation":2364},"/company/contact/#contact-forms","newsletter",{"text":2676,"config":2677},"Press",{"href":2678,"dataGaName":2679,"dataGaLocation":2364},"/press/","press",{"text":2681,"config":2682,"menu":2683},"Contact us",{"dataNavLevelOne":2627},{"type":2415,"columns":2684},[2685],{"items":2686},[2687,2692,2697],{"text":2688,"config":2689},"Talk to sales",{"href":2690,"dataGaName":2691,"dataGaLocation":2364},"/sales/","talk to sales",{"text":2693,"config":2694},"Support portal",{"href":2695,"dataGaName":2696,"dataGaLocation":2364},"https://support.gitlab.com/hc/en-us","support portal",{"text":2698,"config":2699},"Customer portal",{"href":2700,"dataGaName":2701,"dataGaLocation":2364},"https://customers.gitlab.com/customers/sign_in/","customer portal",{"close":2703,"login":2704,"suggestions":2710},"Close",{"text":2705,"link":2706},"To search repositories and projects, login to",{"text":556,"config":2707},{"href":2378,"dataGaName":2708,"dataGaLocation":2709},"search login","search",{"text":2711,"default":2712},"Suggestions",[2713,2715,2719,2721,2725,2729],{"text":2395,"config":2714},{"href":2400,"dataGaName":2395,"dataGaLocation":2709},{"text":2716,"config":2717},"Code Suggestions (AI)",{"href":2718,"dataGaName":2716,"dataGaLocation":2709},"/solutions/code-suggestions/",{"text":2431,"config":2720},{"href":2433,"dataGaName":2431,"dataGaLocation":2709},{"text":2722,"config":2723},"GitLab on AWS",{"href":2724,"dataGaName":2722,"dataGaLocation":2709},"/partners/technology-partners/aws/",{"text":2726,"config":2727},"GitLab on Google Cloud",{"href":2728,"dataGaName":2726,"dataGaLocation":2709},"/partners/technology-partners/google-cloud-platform/",{"text":2730,"config":2731},"Why GitLab?",{"href":2408,"dataGaName":2730,"dataGaLocation":2709},{"freeTrial":2733,"mobileIcon":2738,"desktopIcon":2743,"secondaryButton":2746},{"text":2734,"config":2735},"Start free trial",{"href":2736,"dataGaName":2369,"dataGaLocation":2737},"https://gitlab.com/-/trials/new/","nav",{"altText":2739,"config":2740},"Gitlab Icon",{"src":2741,"dataGaName":2742,"dataGaLocation":2737},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203874/jypbw1jx72aexsoohd7x.svg","gitlab icon",{"altText":2739,"config":2744},{"src":2745,"dataGaName":2742,"dataGaLocation":2737},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203875/gs4c8p8opsgvflgkswz9.svg",{"text":2747,"config":2748},"Get Started",{"href":2749,"dataGaName":2750,"dataGaLocation":2737},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com/get-started/","get started",{"freeTrial":2752,"mobileIcon":2756,"desktopIcon":2758},{"text":2753,"config":2754},"Learn more about GitLab Duo",{"href":2400,"dataGaName":2755,"dataGaLocation":2737},"gitlab duo",{"altText":2739,"config":2757},{"src":2741,"dataGaName":2742,"dataGaLocation":2737},{"altText":2739,"config":2759},{"src":2745,"dataGaName":2742,"dataGaLocation":2737},{"button":2761,"mobileIcon":2766,"desktopIcon":2768},{"text":2762,"config":2763},"/switch",{"href":2764,"dataGaName":2765,"dataGaLocation":2737},"#contact","switch",{"altText":2739,"config":2767},{"src":2741,"dataGaName":2742,"dataGaLocation":2737},{"altText":2739,"config":2769},{"src":2770,"dataGaName":2742,"dataGaLocation":2737},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1773335277/ohhpiuoxoldryzrnhfrh.png",{"freeTrial":2772,"mobileIcon":2777,"desktopIcon":2779},{"text":2773,"config":2774},"Back to pricing",{"href":2513,"dataGaName":2775,"dataGaLocation":2737,"icon":2776},"back to pricing","GoBack",{"altText":2739,"config":2778},{"src":2741,"dataGaName":2742,"dataGaLocation":2737},{"altText":2739,"config":2780},{"src":2745,"dataGaName":2742,"dataGaLocation":2737},{"title":2782,"button":2783,"config":2787},"GitLab Orbit is here: The context layer for AI agents.",{"text":2406,"config":2784},{"href":2785,"dataGaName":2786,"dataGaLocation":2364},"/gitlab-orbit/","orbit",{"layout":2788,"disabled":2350},"release",{"data":2790},{"text":2791,"source":2792,"edit":2798,"contribute":2803,"config":2808,"items":2813,"minimal":3023},"Git is a trademark of Software Freedom Conservancy and our use of 'GitLab' is under license",{"text":2793,"config":2794},"View page source",{"href":2795,"dataGaName":2796,"dataGaLocation":2797},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/","page source","footer",{"text":2799,"config":2800},"Edit this page",{"href":2801,"dataGaName":2802,"dataGaLocation":2797},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/content/","web ide",{"text":2804,"config":2805},"Please contribute",{"href":2806,"dataGaName":2807,"dataGaLocation":2797},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/CONTRIBUTING.md/","please contribute",{"twitter":2809,"facebook":2810,"youtube":2811,"linkedin":2812},"https://twitter.com/gitlab","https://www.facebook.com/gitlab","https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg","https://www.linkedin.com/company/gitlab-com",[2814,2861,2915,2959,2991],{"title":2511,"links":2815,"subMenu":2830},[2816,2820,2825],{"text":2817,"config":2818},"View plans",{"href":2513,"dataGaName":2819,"dataGaLocation":2797},"view plans",{"text":2821,"config":2822},"Why Premium?",{"href":2823,"dataGaName":2824,"dataGaLocation":2797},"/pricing/premium/","why premium",{"text":2826,"config":2827},"Why Ultimate?",{"href":2828,"dataGaName":2829,"dataGaLocation":2797},"/pricing/ultimate/","why ultimate",[2831],{"title":2832,"links":2833},"Contact Us",[2834,2837,2839,2841,2846,2851,2856],{"text":2835,"config":2836},"Contact sales",{"href":2690,"dataGaName":2374,"dataGaLocation":2797},{"text":2693,"config":2838},{"href":2695,"dataGaName":2696,"dataGaLocation":2797},{"text":2698,"config":2840},{"href":2700,"dataGaName":2701,"dataGaLocation":2797},{"text":2842,"config":2843},"Status",{"href":2844,"dataGaName":2845,"dataGaLocation":2797},"https://status.gitlab.com/","status",{"text":2847,"config":2848},"Terms of use",{"href":2849,"dataGaName":2850,"dataGaLocation":2797},"/terms/","terms of use",{"text":2852,"config":2853},"Privacy statement",{"href":2854,"dataGaName":2855,"dataGaLocation":2797},"/privacy/","privacy statement",{"text":2857,"config":2858},"Cookie preferences",{"dataGaName":2859,"dataGaLocation":2797,"id":2860,"isOneTrustButton":137},"cookie preferences","ot-sdk-btn",{"title":2411,"links":2862,"subMenu":2871},[2863,2867],{"text":2864,"config":2865},"DevSecOps platform",{"href":2393,"dataGaName":2866,"dataGaLocation":2797},"devsecops platform",{"text":2868,"config":2869},"AI-Assisted Development",{"href":2400,"dataGaName":2870,"dataGaLocation":2797},"ai-assisted development",[2872],{"title":2873,"links":2874},"Topics",[2875,2880,2885,2890,2895,2900,2905,2910],{"text":2876,"config":2877},"CICD",{"href":2878,"dataGaName":2879,"dataGaLocation":2797},"/topics/ci-cd/","cicd",{"text":2881,"config":2882},"GitOps",{"href":2883,"dataGaName":2884,"dataGaLocation":2797},"/topics/gitops/","gitops",{"text":2886,"config":2887},"DevOps",{"href":2888,"dataGaName":2889,"dataGaLocation":2797},"/topics/devops/","devops",{"text":2891,"config":2892},"Version Control",{"href":2893,"dataGaName":2894,"dataGaLocation":2797},"/topics/version-control/","version control",{"text":2896,"config":2897},"DevSecOps",{"href":2898,"dataGaName":2899,"dataGaLocation":2797},"/topics/devsecops/","devsecops",{"text":2901,"config":2902},"Cloud Native",{"href":2903,"dataGaName":2904,"dataGaLocation":2797},"/topics/cloud-native/","cloud native",{"text":2906,"config":2907},"AI for Coding",{"href":2908,"dataGaName":2909,"dataGaLocation":2797},"/topics/devops/ai-for-coding/","ai for coding",{"text":2911,"config":2912},"Agentic AI",{"href":2913,"dataGaName":2914,"dataGaLocation":2797},"/topics/agentic-ai/","agentic ai",{"title":2916,"links":2917},"Solutions",[2918,2920,2922,2927,2931,2934,2938,2941,2943,2946,2949,2954],{"text":2455,"config":2919},{"href":2450,"dataGaName":2455,"dataGaLocation":2797},{"text":2444,"config":2921},{"href":2427,"dataGaName":2428,"dataGaLocation":2797},{"text":2923,"config":2924},"Agile development",{"href":2925,"dataGaName":2926,"dataGaLocation":2797},"/solutions/agile-delivery/","agile delivery",{"text":2928,"config":2929},"SCM",{"href":2440,"dataGaName":2930,"dataGaLocation":2797},"source code management",{"text":2876,"config":2932},{"href":2433,"dataGaName":2933,"dataGaLocation":2797},"continuous integration & delivery",{"text":2935,"config":2936},"Value stream management",{"href":2483,"dataGaName":2937,"dataGaLocation":2797},"value stream management",{"text":2881,"config":2939},{"href":2940,"dataGaName":2884,"dataGaLocation":2797},"/solutions/gitops/",{"text":2493,"config":2942},{"href":2496,"dataGaName":2497,"dataGaLocation":2797},{"text":2944,"config":2945},"Small business",{"href":2502,"dataGaName":2503,"dataGaLocation":2797},{"text":2947,"config":2948},"Public sector",{"href":2508,"dataGaName":2509,"dataGaLocation":2797},{"text":2950,"config":2951},"Education",{"href":2952,"dataGaName":2953,"dataGaLocation":2797},"/solutions/education/","education",{"text":2955,"config":2956},"Financial services",{"href":2957,"dataGaName":2958,"dataGaLocation":2797},"/solutions/finance/","financial services",{"title":2516,"links":2960},[2961,2963,2965,2967,2970,2972,2975,2977,2979,2981,2983,2985,2987,2989],{"text":2529,"config":2962},{"href":2531,"dataGaName":2532,"dataGaLocation":2797},{"text":2534,"config":2964},{"href":2536,"dataGaName":2537,"dataGaLocation":2797},{"text":2539,"config":2966},{"href":2541,"dataGaName":2542,"dataGaLocation":2797},{"text":2544,"config":2968},{"href":2546,"dataGaName":2969,"dataGaLocation":2797},"docs",{"text":2567,"config":2971},{"href":2569,"dataGaName":2570,"dataGaLocation":2797},{"text":2973,"config":2974},"What's new",{"href":2622,"dataGaName":2623,"dataGaLocation":2797},{"text":2562,"config":2976},{"href":2564,"dataGaName":2565,"dataGaLocation":2797},{"text":2576,"config":2978},{"href":2578,"dataGaName":2579,"dataGaLocation":2797},{"text":2584,"config":2980},{"href":2586,"dataGaName":2587,"dataGaLocation":2797},{"text":2589,"config":2982},{"href":2591,"dataGaName":2592,"dataGaLocation":2797},{"text":2594,"config":2984},{"href":2596,"dataGaName":2597,"dataGaLocation":2797},{"text":2599,"config":2986},{"href":2601,"dataGaName":2602,"dataGaLocation":2797},{"text":2604,"config":2988},{"href":2606,"dataGaName":2607,"dataGaLocation":2797},{"text":2609,"config":2990},{"href":2611,"dataGaName":2612,"dataGaLocation":2797},{"title":2625,"links":2992},[2993,2995,2997,2999,3001,3003,3007,3012,3014,3016,3018],{"text":2633,"config":2994},{"href":2635,"dataGaName":2627,"dataGaLocation":2797},{"text":2638,"config":2996},{"href":2640,"dataGaName":2641,"dataGaLocation":2797},{"text":2646,"config":2998},{"href":2648,"dataGaName":2649,"dataGaLocation":2797},{"text":2651,"config":3000},{"href":2653,"dataGaName":2654,"dataGaLocation":2797},{"text":2656,"config":3002},{"href":2658,"dataGaName":2659,"dataGaLocation":2797},{"text":3004,"config":3005},"Sustainability",{"href":3006,"dataGaName":3004,"dataGaLocation":2797},"/sustainability/",{"text":3008,"config":3009},"Diversity, inclusion and belonging (DIB)",{"href":3010,"dataGaName":3011,"dataGaLocation":2797},"/diversity-inclusion-belonging/","Diversity, inclusion and belonging",{"text":2661,"config":3013},{"href":2663,"dataGaName":2664,"dataGaLocation":2797},{"text":2671,"config":3015},{"href":2673,"dataGaName":2674,"dataGaLocation":2797},{"text":2676,"config":3017},{"href":2678,"dataGaName":2679,"dataGaLocation":2797},{"text":3019,"config":3020},"Modern Slavery Transparency Statement",{"href":3021,"dataGaName":3022,"dataGaLocation":2797},"https://handbook.gitlab.com/handbook/legal/modern-slavery-act-transparency-statement/","modern slavery transparency statement",{"items":3024},[3025,3028,3031],{"text":3026,"config":3027},"Terms",{"href":2849,"dataGaName":2850,"dataGaLocation":2797},{"text":3029,"config":3030},"Cookies",{"dataGaName":2859,"dataGaLocation":2797,"id":2860,"isOneTrustButton":137},{"text":3032,"config":3033},"Privacy",{"href":2854,"dataGaName":2855,"dataGaLocation":2797},[3035],{"id":3036,"title":7,"body":2344,"config":3037,"content":3039,"description":2344,"extension":3043,"meta":3044,"navigation":137,"path":3045,"seo":3046,"stem":3047,"__hash__":3048},"blogAuthors/en-us/blog/authors/michael-friedrich.yml",{"template":3038},"BlogAuthor",{"name":7,"config":3040},{"headshot":3041,"ctfId":3042},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749659879/Blog/Author%20Headshots/dnsmichi-headshot.jpg","dnsmichi","yml",{},"/en-us/blog/authors/michael-friedrich",{},"en-us/blog/authors/michael-friedrich","lJ-nfRIhdG49Arfrxdn1Vv4UppwD51BB13S3HwIswt4",[3050,3058,3066],{"title":3051,"description":3052,"heroImage":3053,"category":2340,"date":3054,"authors":3055,"slug":3057,"externalUrl":2344},"GitLab Transcend Hackathon: What developers built on GitLab Orbit","More than 1,500 developers built AI agents and tools using GitLab Orbit. Learn about the winning projects and questions a live code graph answers in one query.\n","https://res.cloudinary.com/about-gitlab-com/image/upload/v1780996524/sd1vpl8empy26wizarqj.png","2026-07-20",[3056],"Mattias Michaux","gitlab-transcend-hackathon-orbit",{"title":3059,"description":3060,"heroImage":3061,"category":2340,"date":3062,"authors":3063,"slug":3065,"externalUrl":2344},"Turn multi-step software delivery into agentic flows you can trust","GitLab Duo Agent Platform's Custom Flows are now generally available.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1783980535/t3gez0gpayfndrhncunf.png","2026-07-16",[3064],"Ozer Dondurmacioglu","multi-step-software-delivery-with-agentic-flows",{"title":3067,"description":3068,"heroImage":3061,"category":2340,"date":3062,"authors":3069,"slug":3070,"externalUrl":2344},"Bring GitLab Duo Agent Platform to your terminal","Duo CLI is now generally available with GitLab 19.2 – stay in the shell for code, pipelines, and multi-step work.",[3064],"gitlab-duo-cli-generally-available",{"promotions":3072},[3073,3086,3097,3109],{"id":3074,"categories":3075,"header":3076,"text":3077,"button":3078,"image":3083},"ai-modernization",[2340],"Is AI achieving its promise at scale?","Quiz will take 5 minutes or less",{"text":3079,"config":3080},"Get your AI maturity score",{"href":3081,"dataGaName":3082,"dataGaLocation":2570},"/assessments/ai-modernization-assessment/","modernization assessment",{"config":3084},{"src":3085},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138786/qix0m7kwnd8x2fh1zq49.png",{"id":3087,"categories":3088,"header":3089,"text":3077,"button":3090,"image":3094},"devops-modernization",[2355,2899],"Are you just managing tools or shipping innovation?",{"text":3091,"config":3092},"Get your DevOps maturity score",{"href":3093,"dataGaName":3082,"dataGaLocation":2570},"/assessments/devops-modernization-assessment/",{"config":3095},{"src":3096},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138785/eg818fmakweyuznttgid.png",{"id":3098,"categories":3099,"header":3101,"text":3077,"button":3102,"image":3106},"security-modernization",[3100],"security","Are you trading speed for security?",{"text":3103,"config":3104},"Get your security maturity score",{"href":3105,"dataGaName":3082,"dataGaLocation":2570},"/assessments/security-modernization-assessment/",{"config":3107},{"src":3108},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138786/p4pbqd9nnjejg5ds6mdk.png",{"id":3110,"paths":3111,"header":3114,"text":3115,"button":3116,"image":3121},"github-azure-migration",[3112,3113],"migration-from-azure-devops-to-gitlab","integrating-azure-devops-scm-and-gitlab","Is your team ready for GitHub's Azure move?","GitHub is already rebuilding around Azure. Find out what it means for you.",{"text":3117,"config":3118},"See how GitLab compares to GitHub",{"href":3119,"dataGaName":3120,"dataGaLocation":2570},"/compare/gitlab-vs-github/github-azure-migration/","github azure migration",{"config":3122},{"src":3096},{"header":3124,"blurb":3125,"button":3126,"secondaryButton":3131},"Start building faster today","See what your team can do with the intelligent orchestration platform for DevSecOps.\n",{"text":3127,"config":3128},"Get your free trial",{"href":3129,"dataGaName":2369,"dataGaLocation":3130},"https://gitlab.com/-/trial_registrations/new?glm_content=default-saas-trial&glm_source=about.gitlab.com/","feature",{"text":2835,"config":3132},{"href":2690,"dataGaName":2374,"dataGaLocation":3130},1784832159131]