github graphql-java-kickstart/graphql-java-tools v15.0.0

2 hours ago

15.0.0 is a major release. It moves to Java 17, graphql-java 26 and Jackson 3, removes the options deprecated in 14.x, and turns most of the schema warnings added in 14.x into build errors. Check your 14.x startup logs for WARNs from graphql.kickstart.tools before upgrading, since most of them fail the build now.

Requirements

  • Java 17 is the minimum (#923).
  • graphql-java 26 is the minimum (#919). With graphql-java 25, a schema that applies any directive fails to build with a NoSuchFieldError.
  • Jackson 3 (3.2.4, tools.jackson) instead of Jackson 2 (#925, #810). No Jackson 2 support is kept. jackson-datatype-jdk8 isn't needed anymore, Optional and java.time are built into Jackson 3.
  • graphql-java-tools no longer brings in org.antlr:antlr4-runtime (#927). An app that uses antlr directly needs to declare it.
  • ⚠️ graphql-spring-boot doesn't work with 15.0.0. Its GraphQLJavaToolsAutoConfiguration calls SchemaParserDictionary.getDictionary() and builds a Jackson 2 PerFieldObjectMapperProvider, both of which are gone. The project has been archived since December 2023, so there won't be a release that fixes it. Stay on 14.x, move to Spring for GraphQL, or build the SchemaParser bean yourself.

Breaking API changes

  • Jackson 3 mappers (#925). Jackson 3 mappers are immutable, so ObjectMapperConfigurer gets the JsonMapper.Builder instead of the mapper:
    // before
    .objectMapperConfigurer { mapper, _ -> mapper.registerModule(MyModule()).disable(FAIL_ON_UNKNOWN_PROPERTIES) }
    // after
    .objectMapperConfigurer { builder, _ -> builder.addModule(MyModule()).disable(FAIL_ON_UNKNOWN_PROPERTIES) }
    A module added there replaces the library's module of the same type, e.g. a KotlinModule with other features.
    • PerFieldObjectMapperProvider returns a Jackson 3 ObjectMapper and is a fun interface now.
    • PerFieldConfiguringObjectMapperProvider is removed, use objectMapperConfigurer instead. The Kotlin (ObjectMapper, ObjectMapperConfigurerContext) -> Unit overload is removed too, a Kotlin lambda converts to ObjectMapperConfigurer on its own.
    • A resolver that can't convert an argument fails with Jackson's own exception (e.g. MismatchedInputException) rather than an IllegalArgumentException wrapping it.
  • introspectionEnabled is removed (#912, #858), deprecated in 14.5.0. Turn introspection off with graphql-java instead:
    // before
    SchemaParserOptions.newOptions().introspectionEnabled(false)
    // after, for the whole JVM
    Introspection.enabledJvmWide(false)
    // after, per request
    ExecutionInput.newExecutionInput()
        .query(query)
        .graphQLContext(mapOf(Introspection.INTROSPECTION_DISABLED to true))
    An introspection query then fails with an "introspection disabled" error when it runs, instead of the FieldUndefined validation error it got before. __typename keeps working.
  • missingResolverDataFetcher(...) is removed (#915, #846). Replace .missingResolverDataFetcher(fetcher) with .missingResolverDataFetcherProvider { _, _ -> fetcher }.
  • SchemaParserDictionary.getDictionary() and graphql.kickstart.tools.util.BiMap are removed (#908, #900). Use dictionary.toMap(), which exists since 14.4.0.
  • SchemaObjects (#922, #917):
    • dictionary is a Set<GraphQLNamedType> instead of a Set<GraphQLType>. Code that builds or copies SchemaObjects with a Set<GraphQLType> stops compiling:
      // before
      schemaObjects.copy(dictionary = types) // types: Set<GraphQLType>
      // after
      schemaObjects.copy(dictionary = types.mapTo(linkedSetOf()) { it as GraphQLNamedType })
    • The copy overload without schemaAppliedDirectives, hidden since that parameter was added, is removed. Code compiled against a version before it needs a recompile.
  • InvalidSchemaError is removed (#927). Nothing has thrown it since graphql-java 19, so a catch for it can just be deleted.

Schemas that now fail to build

Each of these logged a WARN in 14.x and throws at build now.

  • Invalid applied directives (#914, #884): a directive applied where its definition doesn't allow it, without one of its required arguments, or more than once when it isn't repeatable (also across a type and its extensions). The SchemaError lists every such directive with its line. Move the directive, pass the argument (or give it a default), or declare the directive repeatable.
  • Invalid directives on scalars (#909, #893): a directive with no definition, or an argument its definition doesn't have. 14.x logged Scalar '<name>' is built without its directives from the schema and dropped all of the scalar's directives. Declare the directive, fix the argument name or remove it. To keep an undeclared directive, set allowUndeclaredDirectives(true).
  • Resolver methods missing a parameter for an argument (#911, #889), when they take a DataFetchingEnvironment, GraphQLContext or the contextClass in that argument's place. Add a parameter for each argument, in order, before the environment or context parameter. On 14.x, search your build logs for has no parameter for its argument to find all of them.
  • Resolvers that are JDK dynamic proxies (#913, #868), for example Spring AOP without proxyTargetClass = true. The FieldResolverError names every field that can't be called, even if it's never queried. Use class-based proxies (proxyTargetClass = true in Spring), and add a getter for a public field.
  • Repeated declarations (#916, #863, #865): a type and its extensions, or a single object or input definition, can't declare the same enum value, interface field, union member, object field or input field twice. 14.x kept the first one for enums, interfaces and unions and the last one for objects and input objects. Now the build throws a SchemaError like Type 'Color' declares value 'RED' more than once at line 6, column 5. Remove the repeat, since an extension only adds what the type doesn't have.

Behaviour changes

  • Legacy context fallbacks are removed (#915, #846), deprecated with a WARN since 14.2.0:
    • A custom context class that is only passed as the context object (ExecutionInput.context(...)) is no longer given to resolvers. A resolver with a parameter of that class fails the field with an error saying so. Put it in the GraphQLContext under its class instead, e.g. graphQLContext(mapOf(MyContext::class.java to myContext)).
    • ⚠️ A CoroutineScope that is only passed as the context object is no longer used by suspend resolvers, and nothing is logged or fails. They run in CoroutineScope(Dispatchers.Default), so cancelling your scope no longer cancels them. Put it in the GraphQLContext under the CoroutineScope class key instead, e.g. graphQLContext(mapOf(CoroutineScope::class.java to scope)).
  • Jackson 3 defaults (#925). The library's own mapper keeps two of the Jackson 2 defaults, since the new ones silently lose or change a client's data: an input field the class has no property for fails the request, and enums convert by name(). Other new defaults apply:
    • an explicit null into a primitive input field (e.g. count: Int bound to int) fails instead of giving 0. An omitted or null argument bound to a primitive parameter still gets the Java default.
    • java.time values convert natively and are written as ISO strings, e.g. a LocalDate scalar inside an ObjectNode or Map argument arrives as "2020-01-02".
    • an input field that none of a class's @JsonUnwrapped properties know fails the request, and is reported at build, instead of being dropped.
    • the messages say Unrecognized property instead of Unrecognized field.
    • with your own mapper (objectMapperProvider), all of Jackson 3's defaults apply. Spring's and plain Jackson 3 mappers drop unknown input fields, and the unmapped input field warning says so.
  • When no objectMapperConfigurer is set, all fields share one mapper instead of building one each (#925).
  • Input object field order (#916): input objects list the fields of the type first, then those of each extension in order. The extensions' fields used to come first, so code that reads GraphQLInputObjectType.getFieldDefinitions() by position sees the new order.
  • Nested return values are unwrapped the way the schema build matched them (#918, #905):
    • a generic wrapper that only a custom schemaWrapper introduces is now unwrapped below the outermost value like any other.
    • custom scalars get the unwrapped values of nested wrappers and futures, e.g. [DateTime] from List<CompletableFuture<OffsetDateTime>> gets the OffsetDateTimes. A Coercing that unwrapped them itself now only sees the plain values.
    • a Future that isn't a CompletionStage, nested in a list or another wrapper or returned by a nested wrapper's transformer (unless the return type ends in Object), now fails the field with "graphql-java can't wait on". If your ValueUnboxer called get() on it, return a CompletableFuture instead.
    • a nested generic wrapper is unwrapped with the wrapper registered for its declared type, not the most specific one its value is an instance of. If you registered a subclass as a wrapper of its own, declare that subclass in the return type.
    • a future directly inside another wrapper (Try<CompletableFuture<X>>) now goes through the transformer of a wrapper you registered for its type, which used to be skipped there. With the default future wrappers it still reaches graphql-java as the future the resolver returned. If you need the resolver's own future there, declare it as a type you didn't register a wrapper for (e.g. Try<CompletionStage<X>>) or make the transformer return the future it gets.
  • Custom scalars without visible properties that Jackson writes with a type id (@JsonTypeInfo) or an object id (@JsonIdentityInfo) now fail with an error instead of reaching the resolver as a default instance (#934, #932). In ObjectNode arguments they're passed with their value.
  • ⚠️ A return type that uses DataFetcherResult or Optional twice for one value, like Optional<DataFetcherResult<Optional<T>>> or DataFetcherResult<Optional<DataFetcherResult<T>>>, now fails the build (#940, #939). These types built before but gave graphql-java the wrong value. 14.x logged nothing for them.

New features

  • GenericWrapper.withCompletionStageTransformer registers a wrapper whose transformer returns a future of its content, such as Reactor's Mono::toFuture (#941, #938). A future declared inside it, like Mono<CompletableFuture<Item>>, is then waited on too. Wrappers registered with withTransformer keep their behaviour.

Bug fixes

  • A resolver can return Optional<DataFetcherResult<T>> (#940, #939). Before, graphql-java got the DataFetcherResult as the value.

  • Custom scalars backed by a Kotlin class without visible properties are passed to resolvers with their value, in input objects, Optional parameters and ObjectNode arguments (#931, #926).

  • An application on the module path no longer needs Spring AOP when it doesn't use it (#930).

What's Changed

Full Changelog: v14.5.0...v15.0.0

Don't miss a new graphql-java-tools release

NewReleases is sending notifications on new releases.