Skip to content

Split View: GraphQL 스펙, 거의 4년 만의 릴리스 — September 2025 에디션과 GraphQL.js 17 GA에 들어간 것, 아직 밖에 있는 것

|

GraphQL 스펙, 거의 4년 만의 릴리스 — September 2025 에디션과 GraphQL.js 17 GA에 들어간 것, 아직 밖에 있는 것

들어가며 — 멈춘 줄 알았던 표준이 움직였다

GraphQL 스펙의 마지막 에디션은 오랫동안 October 2021이었습니다. 그 사이 생태계는 계속 굴러갔지만 — Apollo Federation, 각종 게이트웨이와 클라이언트가 각자 진화하는 동안 — 표준 문서 자체는 4년 가까이 멈춰 있었습니다. "GraphQL은 이제 유지보수 모드인가"라는 냉소가 나올 만한 공백이었습니다.

그 공백이 최근 두 번의 릴리스로 깨졌습니다. 2025년 9월, September 2025 에디션이 나왔고 — 공식 발표의 표현대로 October 2021 이후 첫 에디션입니다 — 2026년 6월 15일에는 참조 구현인 GraphQL.js v17이 GA됐습니다. v17의 첫 알파가 npm에 올라온 날짜가 2022년 5월 19일이니, 알파 딱지를 떼는 데 4년이 넘게 걸린 셈입니다.

이 글은 두 릴리스를 하이프 없이 뜯어봅니다. 무엇이 실제로 표준에 들어갔는지, 무엇이 아직도 밖에 있는지(스포일러: @defer@stream), 그리고 지금 당신 팀이 무엇을 해야 하는지. 모든 날짜와 버전은 스펙 원문, npm 레지스트리, GitHub 릴리스에서 직접 확인했습니다.

날짜부터 정확히 — 두 릴리스의 타임라인

GraphQL 스펙 (spec.graphql.org)
  2015-07  첫 워킹 드래프트 (July 2015)
  2021-10-26  October 2021 에디션
  2025-09-03  September 2025 에디션   <- 3년 10개월 만
  2026-06-04  현재 워킹 드래프트 갱신 (다음 에디션 준비)

GraphQL.js (registry.npmjs.org 게시 시각)
  2022-05-19  17.0.0-alpha.1
  2025-06-11  17.0.0-alpha.9
  2026-05-07  17.0.0-beta.0
  2026-06-02  17.0.0-rc.0
  2026-06-15  17.0.0 GA              <- 알파 시작 후 4년 남짓
  2026-07-03  17.0.2 (현재 latest)

  16.x는 계속 패치 중: 16.13.0(2026-02-24), 16.14.2(2026-06-09)

스펙 릴리스 노트에 따르면 September 2025 에디션에는 October 2021 이후 30명의 기여자가 만든 100건 이상의 변경이 들어갔고, 이 릴리스는 첫 드래프트(2015년 7월)로부터 10년이 되는 시점의 릴리스이기도 합니다.

September 2025 에디션 — 4년치 변경에서 건질 것

공식 체인지로그가 꼽는 주요 변경은 다섯 가지입니다. 하나씩 봅니다.

OneOf 입력 객체 — "입력 유니온"의 표준화

GraphQL에는 출력 타입의 유니온은 있어도 입력 타입의 유니온이 없어서, "ID로 찾거나, 이메일로 찾거나, 둘 중 하나만"을 스키마로 표현할 방법이 없었습니다. 다들 필드를 전부 nullable로 열어 두고 리졸버에서 손수 검증했지요. RFC #825로 수년을 끌던 이 문제가 @oneOf로 표준화됐습니다.

input UserUniqueCondition @oneOf {
  id: ID
  username: String
  organizationAndEmail: OrganizationAndEmailInput
}

스펙 문구 그대로, OneOf 입력 객체는 정확히 하나의 필드만 제공되어야 하고 그 값은 null이 아니어야 합니다. 둘을 채우면 요청 에러, 하나를 null로 채워도 에러. 이 검증이 이제 리졸버 코드가 아니라 스펙 수준의 입력 강제(coercion) 규칙입니다. 인트로스펙션에는 __Type.isOneOf 필드가 추가되어 코드젠 도구가 유니온 타입을 정확히 생성할 수 있습니다.

Schema Coordinates — 스키마 요소의 주소 체계

RFC #794는 스키마의 모든 요소에 유일한 문자열 주소를 부여합니다.

Business                          타입
Business.name                     필드
SearchCriteria.filter             입력 필드
SearchFilter.OPEN_NOW             enum 값
Query.searchBusiness(criteria:)   필드 인자
@private                          디렉티브
@private(scope:)                  디렉티브 인자

스펙은 각 요소가 정확히 하나의 좌표를 갖도록 보장합니다. 사소해 보이지만 이건 도구 생태계의 공용 어휘입니다 — 스키마 diff, 린트 규칙, 에러 리포팅, 사용량 추적("이 필드 누가 쓰나")이 지금까지는 도구마다 제각각의 표기를 썼습니다. 실제로 에러에 스키마 좌표를 넣자는 후속 RFC가 이미 올라와 있습니다.

나머지: 문서 설명, deprecation 확대, 전체 유니코드

  • 실행 문서(쿼리·뮤테이션)에도 설명(description)을 붙일 수 있게 됐습니다(#1170). 체인지로그가 명시하는 동기가 흥미로운데, AI 소비자를 위해서입니다 — LLM이 쿼리의 의도를 파악할 컨텍스트를 문서 자체에 담자는 것.
  • deprecation이 스키마 요소 전반으로 확대되고 규칙이 정리됐습니다 — 인터페이스가 deprecate하지 않은 필드를 구현체가 deprecate할 수 없다는 제약, includeDeprecated 인자의 non-null화 등(#1053, #1142).
  • 언어 문법이 전체 유니코드 범위를 지원합니다(#849).
  • 기본값 강제 규칙이 재정의됐고(#793), 실행 알고리즘 서술이 대폭 정리됐습니다(ExecuteSelectionSetExecuteCollectedFields로 교체되는 등).

전체적으로 보면 이번 에디션은 신기능 잔치가 아니라 4년치 정합성 부채 청산 + 두 개의 굵직한 언어 기능(OneOf, Coordinates)입니다. 공식 발표문이 AI-ready를 앞세워 포장하고 있긴 하지만, 실체는 착실한 표준 정비에 가깝습니다.

스펙에 들어가지 않은 것 — defer와 stream은 아직도 RFC다

여기가 이 릴리스에서 가장 정직하게 봐야 할 부분입니다. @defer@stream — 응답을 점진적으로 나눠 보내는 incremental delivery — 는 September 2025 에디션에 없습니다. 스펙 본문을 직접 검색해 확인했습니다. defer라는 단어 자체가 0회 등장합니다. 2026년 6월자 워킹 드래프트에도 없습니다.

이게 얼마나 오래된 이야기인지 감을 잡자면: graphql 패키지의 npm 레지스트리에는 experimental-stream-defer 실험 빌드가 2020년 10월부터 올라와 있고, 그 dist-tag는 지금도 2021년 12월에 게시된 16.1.0 계열 빌드를 가리키고 있습니다. Incremental Delivery RFC는 현재 RFC 2(Draft) 단계이고 — 죽은 건 아닙니다. 이 글을 쓰는 주에도 스펙 드래프트에 커밋이 붙었습니다(2026-07-15). 하지만 5년 넘게 "거의 다 됐다"였던 기능이라는 사실은 변하지 않습니다.

왜 이렇게 오래 걸릴까요. 실행 시맨틱(중복 필드 처리, 에러 전파)과 페이로드 형식을 표준화하는 난이도도 있지만, 실전 데이터가 균질하지 않은 탓도 있습니다. GraphQLConf 2025에서 Meta는 @async라는 자체 디렉티브를 발표하면서 @defer가 남기는 숨은 비용을 이유로 들었습니다 — 세계에서 GraphQL을 가장 크게 쓰는 조직이 defer의 대안을 실험 중이라는 뜻입니다. 표준화 대상 자체가 아직 흔들리는 셈입니다.

실무 함의는 명확합니다. defer/stream을 쓰는 모든 스택은 지금도 스펙 외(pre-spec) 기능을 쓰는 것이고, 페이로드 형식이 바뀔 수 있는 위험을 안고 갑니다. 실제로 바뀌었습니다 — 바로 아래 v17 이야기입니다.

GraphQL.js 17 — 무엇이 깨지고, 무엇이 좋아지나

공식 업그레이드 가이드가 이례적으로 충실하니, 여기서는 판단에 필요한 골자만 추립니다.

깨지는 것들

  • Node.js 요구사항이 ^22.0.0 || ^24.0.0 || ^25.0.0 || >=26.0.0으로 올라갑니다. Node 20 이하에 묶인 환경이라면 여기서 끝입니다.
  • 패키지가 exports 조건부 export로 바뀝니다. 공식 진입점(graphql, graphql/execution 등) 밖의 deep import는 semver 보장이 없다고 명시됐고, 폐기됐던 graphql/subscription 서브패스는 제거됐습니다.
  • 개발 모드가 NODE_ENV 기반 자동 활성화에서 명시적 옵트인(enableDevMode() 또는 development 패키지 조건)으로 바뀝니다.
  • 커스텀 스칼라 API 이름이 정리됩니다 — serialize, parseValue, parseLiteral은 deprecated 별칭으로 남고 coerceOutputValue, coerceInputValue, coerceInputLiteral이 새 이름입니다.
  • subscribe()의 반환 타입이 Promise 고정에서 PromiseOrValue로 바뀌어, Promise를 가정한 TypeScript 코드는 수정이 필요합니다.

실행기 분리 — execute는 단일 결과 전용

v17의 설계에서 가장 눈에 띄는 결정은 안정 기능과 실험 기능의 물리적 분리입니다. execute()는 단일 결과 전용의 안정 실행기가 됐고, 스키마에 @defer/@stream 디렉티브가 있으면 아예 거부합니다. incremental delivery를 쓰려면 이름부터 실험임을 못박은 별도 함수를 불러야 합니다.

import { experimentalExecuteIncrementally } from 'graphql';

const result = await experimentalExecuteIncrementally({ schema, document });

if ('initialResult' in result) {
  send(result.initialResult);
  for await (const subsequent of result.subsequentResults) {
    send(subsequent);
  }
} else {
  send(result); // 점진 전송이 필요 없던 경우
}

알파 시절 defer/stream을 프로덕션에 깐 팀에게 중요한 변경: 페이로드 형식이 바뀌었습니다. 초기 알파의 형식은 pathlabel로 페이로드를 식별하고 필드 데이터가 중복될 수 있었는데, 현재 형식은 pending 항목을 id로 등록하고 completed로 완료를 알리는 방식입니다. 옛 형식이 필요한 호스트를 위해 legacyExecuteIncrementally()가 남아 있지만, 이름이 말하듯 이행 기간용입니다. 스펙 외 기능을 미리 쓰면 이런 청구서가 날아온다는 실례입니다.

에러 전파 쪽에도 실험 기능이 하나 들어왔습니다. @experimental_disableErrorPropagation을 오퍼레이션에 붙이면 non-null 필드의 실행 에러가 부모를 연쇄적으로 null로 만드는 대신 해당 위치만 null이 됩니다 — 정상화 캐시를 쓰는 클라이언트에서 조상 null이 실데이터 갱신처럼 보이는 문제를 겨냥한 것으로, 진행 중인 에러 동작(onError) 스펙 논의의 선행 구현입니다. 이 역시 이름에 experimental이 박혀 있습니다.

운영자에게 유용한 것 — AbortSignal, diagnostics_channel, Harness

깨지는 것 말고 얻는 것도 실질적입니다.

  • AbortSignal 지원이 1급 기능으로 들어왔습니다. 요청 타임아웃, 클라이언트 연결 끊김, 다운스트림 API 취소를 하나의 취소 경로로 통합할 수 있고, 리졸버는 info.getAbortSignal()로 접근합니다. 부분 데이터를 만든 뒤 중단되면 AbortedGraphQLExecutionError가 중단 원인과 부분 결과를 함께 노출합니다.
  • Node.js diagnostics_channel로 파싱·검증·실행·필드 리졸브 등 라이프사이클 이벤트가 발행됩니다. APM 계측이 몽키패치 없이 가능해집니다.
  • GraphQLHarness로 parse/validate/execute/subscribe 단계를 교체할 수 있는 공식 형태가 생겼습니다. 발표문 스스로 Envelop이 증명한 단계 모델을 따랐다고 밝힙니다 — 커뮤니티 패턴이 참조 구현으로 역수입된 사례입니다.
  • 프레임워크 저자용으로 실행 파이프라인의 하위 경계(validateExecutionArgs(), executeRootSelectionSet() 등)가 공개되어, 내부 코드를 복사하지 않고 커스텀 실행기를 만들 수 있습니다.

즉 v17의 실속은 defer/stream이 아니라 서버 운영 표면 — 취소, 계측, 확장 지점 — 에 있습니다.

페더레이션 표준화의 현주소 — Composite Schemas는 Stage 0

"연합 GraphQL의 표준화"를 기다리는 분들을 위한 상태 점검입니다. 이전 글에서 다룬 Apollo Federation은 어디까지나 한 벤더의 스펙이고, GraphQL Foundation은 2024년 5월부터 Composite Schemas 워킹그룹에서 벤더 중립 표준을 만들고 있습니다.

2026년 7월 현재의 정직한 상태: 스펙 저장소의 README는 여전히 Stage 0(Preliminary) — Draft 단계 전이며 내용이 바뀔 수 있다고 명시합니다. GitHub 릴리스는 0건입니다. 다만 죽은 프로젝트는 전혀 아닙니다 — 2026년 7월 9일에도 @key·@require 규칙을 다듬는 커밋이 여럿 병합됐고, 2026년 5월 GraphQLConf의 키노트에서 The Guild의 Uri Goldshtein은 페더레이션이 더 이상 프런티어 실험이 아니라 업계가 자신 있게 걷는 잘 닦인 길이 됐다고 진단했습니다. 실무 관점의 결론: 연합 아키텍처를 지금 설계한다면 여전히 벤더 스펙(Apollo Federation v2 계열)을 고르는 것이고, Composite Schemas는 "언젠가 갈아탈 수 있는 방향성"으로만 추적하면 됩니다.

거버넌스 쪽 변화도 하나 기록해 둘 만합니다. 2026년 6월 발표된 GAPs(GraphQL Auxiliary Proposals)는 코어 스펙이 의도적으로 다루지 않는 관례 — 페이지네이션 커넥션, @cost 같은 것들 — 를 커뮤니티 스펙으로 모으는 공식 저장소입니다. 코어 스펙은 좁게, 주변 관례는 GAPs로, 분산 실행은 Composite Schemas로 — 표준화가 세 층으로 분화하는 그림입니다. 참고로 같은 시기 Meta는 GraphQLConf 2026 키노트("The Creator's Curse")에서 10년 전 "배우고 쓰기 쉽다"던 약속이 수천 엔지니어 규모에서 전부 지켜지진 않았다고 자성하며 내부 재설계를 이야기했고, Apollo CEO Matt DeBergalis는 "GraphQL can and must be the language of AI"라고 선언했습니다. 표준은 정비 모드, 벤더는 AI 서사 — 온도차가 있는 시점입니다.

그래서 지금 무엇을 해야 하나

판단 기준으로 정리합니다.

v17로 올라갈 이유가 분명한 경우

  • 서버 프레임워크·게이트웨이·계측 도구를 만드는 입장 — 실행 경계 API, Harness, diagnostics_channel이 목적 그 자체입니다.
  • 요청 취소가 실제 문제인 서비스 — AbortSignal 통합은 v16에서 깔끔한 대안이 없습니다.
  • 신규 프로젝트 — 이제 v17이 latest이고, 문서·API 레퍼런스도 v17 기준으로 재편됐습니다.

서두르지 않아도 되는 경우

  • Node 22 미만에 묶인 환경. 엔진 요구사항이 하드 게이트입니다.
  • deep import나 내부 API에 의존하는 커스텀 코드가 많은 코드베이스 — exports 강제와 실행기 분리로 손볼 곳이 늘어납니다.
  • 16.x는 버려진 브랜치가 아닙니다 — 2026년 6월에도 16.14.2 패치가 나왔습니다. 당장 기능이 아쉽지 않다면 주변 라이브러리들의 peerDependencies가 17을 받는지 확인하고 움직여도 늦지 않습니다.

defer/stream에 대해서는 더 보수적으로

  • 신규 도입이라면: 스펙이 아직 RFC 2이고, v17에서도 함수 이름에 experimental이 박혀 있습니다. 페이로드 형식이 알파 시절 한 번 갈아엎어졌다는 사실이 최고의 경고입니다. 게이트웨이·클라이언트·캐시까지 전 구간이 같은 형식을 지원하는지 확인 전에는 켜지 마십시오.
  • 이미 알파 형식으로 운영 중이라면: legacyExecuteIncrementally()로 시간을 벌 수 있지만, 새 pending/completed 형식으로의 이행 계획을 지금 세우는 편이 좋습니다.

덧붙여, 스키마 설계 관점에서 당장 공짜로 얻을 수 있는 것은 @oneOf입니다. "여러 키 중 하나로 조회" 패턴의 입력 타입을 갖고 있다면, 리졸버 검증 코드를 스키마 선언으로 옮기는 리팩터링은 위험이 낮고 클라이언트 타입 생성 품질이 바로 좋아집니다. 물론 이것도 서버·클라이언트 라이브러리가 September 2025 에디션을 구현했는지 확인이 먼저입니다 — 스펙 릴리스와 생태계 구현 사이의 시차는 늘 있습니다.

마치며

정리하면 이렇습니다. GraphQL 표준은 거의 4년의 침묵을 깨고 September 2025 에디션으로 돌아왔고, 내용물은 화려한 신기능보다 정합성 청산 + OneOf + Schema Coordinates라는 실속형입니다. 참조 구현 GraphQL.js 17은 4년 알파 끝에 GA로 나오면서 안정 실행과 실험 기능(defer/stream, 에러 전파 제어)의 경계를 코드 수준에서 그었습니다. 한편 defer/stream은 10년째 표준 밖이고, 페더레이션 표준(Composite Schemas)은 Stage 0입니다.

이 그림을 냉소로 읽을 수도 있습니다 — "4년 걸려 이거냐". 하지만 반대로 읽는 편이 실무에는 유용합니다. 이 프로젝트는 깨지는 변경을 스펙에 넣는 대신 10년을 기다리는 쪽을 고르는 프로젝트이고, 그래서 October 2021 스펙으로 짠 서버가 지금도 유효합니다. 안정성이 기본값이고 실험은 이름표를 달고 온다 — API 계층의 기반 기술에게 바랄 만한 성질입니다. 비슷한 시기 Protobuf가 Edition 체계로 기본값을 조이는 방식과 비교해 보면, 두 진영 모두 "혁신보다 이행 비용 관리"로 수렴하는 중이라는 점이 보입니다. 표준의 속도에 실망할 시간에, @oneOf 리팩터링과 v17 업그레이드 견적부터 내 보시길.

참고 자료

GraphQL Spec, Almost Four Years Later — What Made It Into the September 2025 Edition and GraphQL.js 17 GA, and What Didn't

Introduction — A Standard That Seemed Frozen, Moving Again

For a long time, the GraphQL spec's last edition was October 2021. In the meantime the ecosystem kept moving — Apollo Federation and various gateways and clients each evolved on their own — but the standard document itself sat frozen for almost four years. It was a gap wide enough to invite the cynical question, "Is GraphQL in maintenance mode now?"

That gap was broken by two recent releases. In September 2025, the September 2025 edition shipped — in the words of the official announcement, the first edition since October 2021 — and on June 15, 2026, the reference implementation, GraphQL.js v17, went GA. The first v17 alpha landed on npm on May 19, 2022, which means it took more than four years to shed the alpha label.

This post takes the two releases apart without the hype: what actually made it into the standard, what's still missing (spoiler: @defer and @stream), and what your team should do about it right now. Every date and version here was verified directly against the spec text, the npm registry, and GitHub releases.

Get the Dates Right First — Timeline of the Two Releases

GraphQL Spec (spec.graphql.org)
  2015-07  first working draft (July 2015)
  2021-10-26  October 2021 edition
  2025-09-03  September 2025 edition   <- 3 years, 10 months later
  2026-06-04  current working draft updated (prepping the next edition)

GraphQL.js (registry.npmjs.org publish timestamps)
  2022-05-19  17.0.0-alpha.1
  2025-06-11  17.0.0-alpha.9
  2026-05-07  17.0.0-beta.0
  2026-06-02  17.0.0-rc.0
  2026-06-15  17.0.0 GA              <- a little over 4 years after alpha began
  2026-07-03  17.0.2 (current latest)

  16.x is still getting patched: 16.13.0 (2026-02-24), 16.14.2 (2026-06-09)

According to the spec's release notes, the September 2025 edition folds in more than 100 changes from 30 contributors since October 2021, and it also happens to land ten years after the first draft (July 2015).

The September 2025 Edition — What's Worth Taking From Four Years of Changes

The official changelog lists five major changes. Let's take them one at a time.

OneOf Input Objects — Standardizing the "Input Union"

GraphQL has had unions for output types but not for input types, so there was no way to express "look up by ID, or by email, but only one of the two" directly in the schema. Everyone left every field nullable and hand-validated in the resolver. This problem, dragging on for years under RFC #825, has now been standardized as @oneOf.

input UserUniqueCondition @oneOf {
  id: ID
  username: String
  organizationAndEmail: OrganizationAndEmailInput
}

In the spec's own words, exactly one field of a OneOf input object must be provided, and its value must not be null. Fill in two fields and you get a request error; fill in one with null and it's also an error. That validation is now a spec-level input coercion rule, not resolver code. Introspection gains a __Type.isOneOf field so codegen tools can generate the union type correctly.

Schema Coordinates — An Addressing System for Schema Elements

RFC #794 gives every element of a schema a unique string address.

Business                          type
Business.name                     field
SearchCriteria.filter             input field
SearchFilter.OPEN_NOW             enum value
Query.searchBusiness(criteria:)   field argument
@private                          directive
@private(scope:)                  directive argument

The spec guarantees each element has exactly one coordinate. It looks trivial, but it's a shared vocabulary for the tooling ecosystem — schema diffs, lint rules, error reporting, and usage tracking ("who's using this field") had each used their own notation until now. There's already a follow-up RFC proposing that errors carry schema coordinates.

The Rest: Document Descriptions, Expanded Deprecation, Full Unicode

  • Executable documents (queries and mutations) can now carry descriptions too (#1170). The changelog's stated motivation is interesting — it's for AI consumers, so the context an LLM needs to understand a query's intent can live in the document itself.
  • Deprecation now extends across schema elements more broadly, with the rules tightened up — a constraint that an implementing type can't deprecate a field its interface hasn't deprecated, the includeDeprecated argument going non-null, and more (#1053, #1142).
  • The language grammar now supports the full Unicode range (#849).
  • Default-value coercion rules were redefined (#793), and the execution algorithm's description was substantially cleaned up (ExecuteSelectionSet replaced by ExecuteCollectedFields, among other changes).

Taken as a whole, this edition isn't a feast of new features — it's four years of consistency debt paid down, plus two substantial language features (OneOf, Coordinates). The official announcement frames it around being AI-ready, but the substance is closer to solid standards maintenance.

What Didn't Make the Spec — defer and stream Are Still an RFC

This is the part of the release that deserves the most honest look. @defer and @stream — incremental delivery, splitting a response and sending it out in pieces — are not in the September 2025 edition. I confirmed this by searching the spec text directly: the word "defer" appears zero times. It's not in the June 2026 working draft either.

To get a sense of how old this story is: the graphql package's npm registry has carried an experimental-stream-defer experimental build since October 2020, and that dist-tag still points to a 16.1.0-series build published in December 2021. The Incremental Delivery RFC currently sits at RFC 2 (Draft) — it's not dead. A commit landed on the spec draft the very week I'm writing this (2026-07-15). But that doesn't change the fact that it's been "almost done" for more than five years.

Why does it take this long? Part of it is the difficulty of standardizing execution semantics (duplicate field handling, error propagation) and the payload format, but part of it is also that real-world data isn't uniform. At GraphQLConf 2025, Meta announced its own directive called @async, citing the hidden costs @defer leaves behind — which means the organization running GraphQL at the largest scale in the world is experimenting with an alternative to defer. The very target of standardization is still shifting under everyone's feet.

The practical implication is clear. Any stack using defer/stream today is running a pre-spec feature and carries the risk that the payload format can change. It already has — that's the v17 story right below.

GraphQL.js 17 — What Breaks, What Gets Better

The official upgrade guide is unusually thorough, so here I'll just pull out the essentials you need to make a call.

What Breaks

  • The Node.js requirement rises to ^22.0.0 || ^24.0.0 || ^25.0.0 || >=26.0.0. If your environment is locked to Node 20 or below, this is where it stops.
  • The package switches to exports-based conditional exports. Deep imports outside the official entry points (graphql, graphql/execution, etc.) are now explicitly stated to have no semver guarantee, and the already-deprecated graphql/subscription subpath has been removed.
  • Development mode switches from automatic NODE_ENV-based activation to explicit opt-in (enableDevMode() or the development package condition).
  • Custom scalar API names are cleaned up — serialize, parseValue, and parseLiteral remain as deprecated aliases, while coerceOutputValue, coerceInputValue, and coerceInputLiteral are the new names.
  • subscribe()'s return type changes from a fixed Promise to PromiseOrValue, so TypeScript code that assumed a Promise will need fixing.

Executor Separation — execute Is Single-Result Only

The most conspicuous design decision in v17 is the physical separation of stable and experimental features. execute() is now a stable, single-result-only executor, and it outright rejects a schema that has @defer/@stream directives. To use incremental delivery you have to call a separate function whose name nails down that it's experimental.

import { experimentalExecuteIncrementally } from 'graphql';

const result = await experimentalExecuteIncrementally({ schema, document });

if ('initialResult' in result) {
  send(result.initialResult);
  for await (const subsequent of result.subsequentResults) {
    send(subsequent);
  }
} else {
  send(result); // for cases that didn't need incremental delivery
}

A change that matters to teams who put defer/stream into production during the alpha years: the payload format changed. The early alpha format identified payloads by path and label, and field data could be duplicated; the current format registers pending entries by id and announces completion with completed. legacyExecuteIncrementally() remains for hosts that need the old format, but as the name says, it's for a transition period. This is a concrete example of the bill that comes due for using a pre-spec feature early.

There's also one experimental feature on the error-propagation side. Attaching @experimental_disableErrorPropagation to an operation makes an execution error on a non-null field null out just that location instead of cascading null up through its ancestors — aimed at the problem where, in clients using normalized caches, a nulled-out ancestor gets mistaken for a real data update. It's a preview implementation of the ongoing error-behavior (onError) spec discussion, and again, "experimental" is baked right into the name.

What's Useful for Operators — AbortSignal, diagnostics_channel, Harness

Beyond what breaks, what you gain is substantial too.

  • AbortSignal support arrives as a first-class feature. Request timeouts, client disconnects, and downstream API cancellation can now be unified into a single cancellation path, and resolvers access it via info.getAbortSignal(). If execution is aborted after partial data has been produced, AbortedGraphQLExecutionError exposes both the abort reason and the partial result.
  • Lifecycle events for parsing, validation, execution, and field resolution are now published via Node.js diagnostics_channel. APM instrumentation becomes possible without monkey-patching.
  • GraphQLHarness gives you an official way to swap out the parse/validate/execute/subscribe stages. The announcement itself states that it follows the staged model Envelop proved out — a case of a community pattern being re-imported into the reference implementation.
  • For framework authors, lower-level boundaries of the execution pipeline (validateExecutionArgs(), executeRootSelectionSet(), etc.) are now exposed, letting you build a custom executor without copying internal code.

In other words, v17's real substance isn't defer/stream — it's the server operations surface: cancellation, instrumentation, extension points.

Where Federation Standardization Stands — Composite Schemas Is at Stage 0

A status check for anyone waiting on "standardization of federated GraphQL." The Apollo Federation covered in an earlier post is, after all, just one vendor's spec, and since May 2024 the GraphQL Foundation has been building a vendor-neutral standard in the Composite Schemas Working Group.

The honest state of things as of July 2026: the spec repository's README still states Stage 0 (Preliminary) — pre-Draft, and content is subject to change. GitHub releases: zero. That said, it's nowhere near a dead project — several commits refining the @key/@require rules merged as recently as July 9, 2026, and in a May 2026 GraphQLConf keynote, The Guild's Uri Goldshtein assessed that federation is no longer a frontier experiment but a well-paved road the industry now walks with confidence. The practical takeaway: if you're designing a federated architecture right now, you're still picking a vendor spec (the Apollo Federation v2 family), and Composite Schemas is something to track only as "a direction you might switch to someday."

One governance-side change is also worth recording. GAPs (GraphQL Auxiliary Proposals), announced in June 2026, is an official repository that gathers conventions the core spec deliberately doesn't cover — things like pagination connections and @cost — into community specs. Core spec kept narrow, surrounding conventions routed to GAPs, distributed execution routed to Composite Schemas — the picture is standardization splitting into three layers. For what it's worth, around the same time, Meta used its GraphQLConf 2026 keynote ("The Creator's Curse") to reflect that the ten-year-old promise of being "easy to learn and use" hadn't fully held up at a scale of thousands of engineers, and talked about an internal redesign, while Apollo CEO Matt DeBergalis declared that "GraphQL can and must be the language of AI." The standard is in maintenance mode while vendors tell an AI story — there's a temperature gap at this moment.

So What Should You Do Now

Here's a summary as decision criteria.

Clear reasons to move to v17

  • You're building a server framework, gateway, or instrumentation tool — the execution boundary APIs, Harness, and diagnostics_channel are the point in themselves.
  • Request cancellation is a real problem for your service — there's no clean alternative to AbortSignal integration in v16.
  • A new project — v17 is now latest, and the docs and API reference have been reorganized around v17.

Cases where there's no need to rush

  • Environments locked to below Node 22. The engine requirement is a hard gate.
  • Codebases with a lot of custom code relying on deep imports or internal APIs — enforced exports and executor separation mean more spots to fix.
  • 16.x isn't an abandoned branch — a 16.14.2 patch shipped as recently as June 2026. If you're not missing any feature right now, it's not too late to move once you've confirmed the peerDependencies of your surrounding libraries accept 17.

Be more conservative about defer/stream

  • If this is a new adoption: the spec is still at RFC 2, and even in v17 the function name has "experimental" baked in. The fact that the payload format got overhauled once already during the alpha years is the best warning sign there is. Don't turn it on until you've confirmed the gateway, client, and cache all the way through support the same format.
  • If you're already running on the alpha format: legacyExecuteIncrementally() can buy you time, but it's better to set a migration plan toward the new pending/completed format now.

One more thing — from a schema-design standpoint, @oneOf is something you can get for free right away. If you have input types with a "look up by one of several keys" pattern, the refactor of moving resolver validation code into a schema declaration is low-risk, and client type-generation quality improves immediately. Of course, this too requires first confirming that your server and client libraries have implemented the September 2025 edition — there's always a lag between a spec release and ecosystem implementation.

Closing

To sum up: the GraphQL standard broke almost four years of silence and returned with the September 2025 edition, and its contents lean practical — consistency debt paid down plus OneOf plus Schema Coordinates — rather than a showcase of flashy new features. The reference implementation, GraphQL.js 17, came out of four years of alpha as a GA release that drew the boundary between stable execution and experimental features (defer/stream, error-propagation control) at the code level. Meanwhile defer/stream is on its tenth year outside the standard, and the federation standard (Composite Schemas) sits at Stage 0.

You could read this picture cynically — "four years for this?" But reading it the other way round is more useful in practice. This is a project that chooses to wait ten years rather than put a breaking change into the spec, which is exactly why a server built against the October 2021 spec is still valid today. Stability is the default, and experiments come labeled as such — that's a property worth wanting from the foundational technology of your API layer. Compared against how Protobuf tightens its defaults through an Edition system around the same time, you can see both camps converging on "managing migration cost over chasing innovation." Instead of spending time disappointed at the standard's pace, go draft your @oneOf refactor and your v17 upgrade estimate.

References