GraphQL and Spring Boot 2.0.3
Asked Answered
E

1

10

I want to test GraphQL in a project that also uses Spring Boot, Spring Security; and I hope to use the latest [as of writing] Spring Boot 2.0.3.RELEASE. As described in more detail below, I found that an older version of GraphQL along with an older Spring Boot solved an initial problem I faced (also described below). I am hoping to solve how to use the latest versions of all these dependencies (without any of the below problems).

Here is how I started from the beginning: So I created a new Spring Boot App in IntelliJ using the build in Spring Initializr. The Spring Boot version is, of course, the latest (2.0.3.RELEASE) Now I add the dependencies for GraphQL and GraphiQL.

<dependency>
    <groupId>com.graphql-java</groupId>
    <artifactId>graphql-spring-boot-starter</artifactId>
    <version>4.3.0</version>
</dependency>
<dependency>
    <groupId>com.graphql-java</groupId>
    <artifactId>graphql-java-tools</artifactId>
    <version>5.2.0</version>
</dependency>
<dependency>
    <groupId>com.graphql-java</groupId>
    <artifactId>graphiql-spring-boot-starter</artifactId>
    <version>4.3.0</version>
</dependency>

To test this, I ran the application but there was no /graphql endpoint. In this case, I configured it in application.properties without a problem:

# GraphQL
graphql.servlet.mapping=/graphql
graphql.servlet.enabled=true
graphql.servlet.corsEnabled=true
# GraphiQL
graphiql.mapping=/graphiql
graphiql.endpoint=/graphql
graphiql.enabled=true
graphiql.cdn.enabled=true
graphiql.cdn.version=0.11.11

After testing again, the endpoint is there, so now I could write a schema, resolvers and so on. This is what I implemented:

Schema:

schema {
    query: Query
    mutation: Mutation
}

type Greeting {
    id: ID!
    message: String!
}

type Query {
    greetingsAll: [Greeting]
}

type Mutation {
    greeting(message: String!): Greeting
}

Model:

@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
@Builder
@ToString
@Entity
public class Greeting {
    @Id
    @GeneratedValue
    private Long id;
    private String message;
}

Repository

@Repository
public interface GreetingRepository extends JpaRepository<Greeting, Long> {
}

QueryResolver

@Component
public class QueryResolver implements GraphQLQueryResolver {

    @Autowired
    private GreetingRepository greetingRepository;

    public Greeting greeting(Long id) {
        return greetingRepository.getOne(id);
    }

    public Iterable<Greeting> getGreetingsAll() {
        return greetingRepository.findAll();
    }
}

MutationResolver

@Component
public class MutationResolver implements GraphQLMutationResolver {

    @Autowired
    private GreetingRepository greetingRepository;

    public Greeting newGreeting(String message) {
        Greeting greeting = new Greeting();
        greeting.setMessage(message);

        return greetingRepository.save(greeting);
    }
}

Beans for SpringBoot

@SpringBootApplication
public class GraphqlApplication {

    public static void main(String[] args) {
        SpringApplication.run(GraphqlApplication.class, args);
    }

    @Bean
    ApplicationRunner init(GreetingRepository greetingRepository) {
        return args -> {
            Stream.of("Hallo", "Guten Tag", "Moin").forEach(greeting -> greetingRepository.save(Greeting.builder().message(greeting).build()));
            greetingRepository.findAll().forEach(System.out::println);
        };
    }

    @Bean
    public QueryResolver query() {
        return new QueryResolver();
    }

    @Bean
    public MutationResolver mutation() {
        return new MutationResolver();
    }
}

While trying to test the application again to see if I could run a query using GraphiQL, the application wouldn't start:

org.springframework.context.ApplicationContextException: Unable to start web server; nested exception is org.springframework.boot.web.server.WebServerException: Unable to start embedded Tomcat
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh(ServletWebServerApplicationContext.java:155) ~[spring-boot-2.0.3.RELEASE.jar:2.0.3.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:544) ~[spring-context-5.0.7.RELEASE.jar:5.0.7.RELEASE]
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:140) ~[spring-boot-2.0.3.RELEASE.jar:2.0.3.RELEASE]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:759) [spring-boot-2.0.3.RELEASE.jar:2.0.3.RELEASE]
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:395) [spring-boot-2.0.3.RELEASE.jar:2.0.3.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:327) [spring-boot-2.0.3.RELEASE.jar:2.0.3.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1255) [spring-boot-2.0.3.RELEASE.jar:2.0.3.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1243) [spring-boot-2.0.3.RELEASE.jar:2.0.3.RELEASE]
    at de.eno.prototyp.graphql.GraphqlApplication.main(GraphqlApplication.java:14) [classes/:na]
Caused by: org.springframework.boot.web.server.WebServerException: Unable to start embedded Tomcat
    at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.initialize(TomcatWebServer.java:126) ~[spring-boot-2.0.3.RELEASE.jar:2.0.3.RELEASE]
    at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.<init>(TomcatWebServer.java:86) ~[spring-boot-2.0.3.RELEASE.jar:2.0.3.RELEASE]
    at org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory.getTomcatWebServer(TomcatServletWebServerFactory.java:413) ~[spring-boot-2.0.3.RELEASE.jar:2.0.3.RELEASE]
    at org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory.getWebServer(TomcatServletWebServerFactory.java:174) ~[spring-boot-2.0.3.RELEASE.jar:2.0.3.RELEASE]
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.createWebServer(ServletWebServerApplicationContext.java:179) ~[spring-boot-2.0.3.RELEASE.jar:2.0.3.RELEASE]
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh(ServletWebServerApplicationContext.java:152) ~[spring-boot-2.0.3.RELEASE.jar:2.0.3.RELEASE]
    ... 8 common frames omitted
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'graphQLServletRegistrationBean' defined in class path resource [com/oembedler/moon/graphql/boot/GraphQLWebAutoConfiguration.class]: Unsatisfied dependency expressed through method 'graphQLServletRegistrationBean' parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'graphQLServlet' defined in class path resource [com/oembedler/moon/graphql/boot/GraphQLWebAutoConfiguration.class]: Unsatisfied dependency expressed through method 'graphQLServlet' parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'graphQLSchemaProvider' defined in class path resource [com/oembedler/moon/graphql/boot/GraphQLWebAutoConfiguration.class]: Unsatisfied dependency expressed through method 'graphQLSchemaProvider' parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'graphQLSchema' defined in class path resource [com/oembedler/moon/graphql/boot/GraphQLJavaToolsAutoConfiguration.class]: Unsatisfied dependency expressed through method 'graphQLSchema' parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'schemaParser' defined in class path resource [com/oembedler/moon/graphql/boot/GraphQLJavaToolsAutoConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.coxautodev.graphql.tools.SchemaParser]: Factory method 'schemaParser' threw exception; nested exception is com.coxautodev.graphql.tools.FieldResolverError: Found more than one matching resolver for field 'FieldDefinition{name='greetingsAll', type=ListType{type=TypeName{name='Greeting'}}, inputValueDefinitions=[], directives=[]}': [MethodFieldResolver{method=public java.lang.Iterable de.eno.prototyp.graphql.QueryResolver.getGreetingsAll()}, MethodFieldResolver{method=public java.lang.Iterable de.eno.prototyp.graphql.QueryResolver.getGreetingsAll()}]
    at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:732) ~[spring-beans-5.0.7.RELEASE.jar:5.0.7.RELEASE]
    at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:474) ~[spring-beans-5.0.7.RELEASE.jar:5.0.7.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1256) 
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'graphQLServlet' defined in class path resource [com/oembedler/moon/graphql/boot/GraphQLWebAutoConfiguration.class]: Unsatisfied dependency expressed through method 'graphQLServlet' parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'graphQLSchemaProvider' defined in class path resource [com/oembedler/moon/graphql/boot/GraphQLWebAutoConfiguration.class]: Unsatisfied dependency expressed through method 'graphQLSchemaProvider' parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'graphQLSchema' defined in class path resource [com/oembedler/moon/graphql/boot/GraphQLJavaToolsAutoConfiguration.class]: Unsatisfied dependency expressed through method 'graphQLSchema' parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'schemaParser' defined in class path resource [com/oembedler/moon/graphql/boot/GraphQLJavaToolsAutoConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.coxautodev.graphql.tools.SchemaParser]: Factory method 'schemaParser' threw exception; nested exception is com.coxautodev.graphql.tools.FieldResolverError: Found more than one matching resolver for field 'FieldDefinition{name='greetingsAll', type=ListType{type=TypeName{name='Greeting'}}, inputValueDefinitions=[], directives=[]}': [MethodFieldResolver{method=public java.lang.Iterable de.eno.prototyp.graphql.QueryResolver.getGreetingsAll()}, MethodFieldResolver{method=public java.lang.Iterable de.eno.prototyp.graphql.QueryResolver.getGreetingsAll()}]
    at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:732) ~[spring-beans-5.0.7.RELEASE.jar:5.0.7.RELEASE]
    at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:474) ~[spring-beans-5.0.7.RELEASE.jar:5.0.7.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1256) ~[spring-beans-5.0.7.RELEASE.jar:5.0.7.RELEASE]
    ... 24 common frames omitted
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'graphQLSchemaProvider' defined in class path resource [com/oembedler/moon/graphql/boot/GraphQLWebAutoConfiguration.class]: Unsatisfied dependency expressed through method 'graphQLSchemaProvider' parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'graphQLSchema' defined in class path resource [com/oembedler/moon/graphql/boot/GraphQLJavaToolsAutoConfiguration.class]: Unsatisfied dependency expressed through method 'graphQLSchema' parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'schemaParser' defined in class path resource [com/oembedler/moon/graphql/boot/GraphQLJavaToolsAutoConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.coxautodev.graphql.tools.SchemaParser]: Factory method 'schemaParser' threw exception; nested exception is com.coxautodev.graphql.tools.FieldResolverError: Found more than one matching resolver for field 'FieldDefinition{name='greetingsAll', type=ListType{type=TypeName{name='Greeting'}}, inputValueDefinitions=[], directives=[]}': [MethodFieldResolver{method=public java.lang.Iterable de.eno.prototyp.graphql.QueryResolver.getGreetingsAll()}, MethodFieldResolver{method=public java.lang.Iterable de.eno.prototyp.graphql.QueryResolver.getGreetingsAll()}]
    at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:732) ~[spring-beans-5.0.7.RELEASE.jar:5.0.7.RELEASE]
    at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:474) ~[spring-beans-5.0.7.RELEASE.jar:5.0.7.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1256) ~[spring-beans-5.0.7.RELEASE.jar:5.0.7.RELEASE]
    ... 38 common frames omitted
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'graphQLSchema' defined in class path resource [com/oembedler/moon/graphql/boot/GraphQLJavaToolsAutoConfiguration.class]: Unsatisfied dependency expressed through method 'graphQLSchema' parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'schemaParser' defined in class path resource [com/oembedler/moon/graphql/boot/GraphQLJavaToolsAutoConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.coxautodev.graphql.tools.SchemaParser]: Factory method 'schemaParser' threw exception; nested exception is com.coxautodev.graphql.tools.FieldResolverError: Found more than one matching resolver for field 'FieldDefinition{name='greetingsAll', type=ListType{type=TypeName{name='Greeting'}}, inputValueDefinitions=[], directives=[]}': [MethodFieldResolver{method=public java.lang.Iterable de.eno.prototyp.graphql.QueryResolver.getGreetingsAll()}, MethodFieldResolver{method=public java.lang.Iterable de.eno.prototyp.graphql.QueryResolver.getGreetingsAll()}]
    at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:732) ~[spring-beans-5.0.7.RELEASE.jar:5.0.7.RELEASE]
    at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:474) 
    ... 52 common frames omitted
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'schemaParser' defined in class path resource [com/oembedler/moon/graphql/boot/GraphQLJavaToolsAutoConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.coxautodev.graphql.tools.SchemaParser]: Factory method 'schemaParser' threw exception; nested exception is com.coxautodev.graphql.tools.FieldResolverError: Found more than one matching resolver for field 'FieldDefinition{name='greetingsAll', type=ListType{type=TypeName{name='Greeting'}}, inputValueDefinitions=[], directives=[]}': [MethodFieldResolver{method=public java.lang.Iterable de.eno.prototyp.graphql.QueryResolver.getGreetingsAll()}, MethodFieldResolver{method=public java.lang.Iterable de.eno.prototyp.graphql.QueryResolver.getGreetingsAll()}]
    at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:590) ~[spring-beans-5.0.7.RELEASE.jar:5.0.7.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1256) 
    ... 66 common frames omitted
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.coxautodev.graphql.tools.SchemaParser]: Factory method 'schemaParser' threw exception; nested exception is com.coxautodev.graphql.tools.FieldResolverError: Found more than one matching resolver for field 'FieldDefinition{name='greetingsAll', type=ListType{type=TypeName{name='Greeting'}}, inputValueDefinitions=[], directives=[]}': [MethodFieldResolver{method=public java.lang.Iterable de.eno.prototyp.graphql.QueryResolver.getGreetingsAll()}, MethodFieldResolver{method=public java.lang.Iterable de.eno.prototyp.graphql.QueryResolver.getGreetingsAll()}]
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185) ~[spring-beans-5.0.7.RELEASE.jar:5.0.7.RELEASE]
    at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:582) ~[spring-beans-5.0.7.RELEASE.jar:5.0.7.RELEASE]
    ... 79 common frames omitted
Caused by: com.coxautodev.graphql.tools.FieldResolverError: Found more than one matching resolver for field 'FieldDefinition{name='greetingsAll', type=ListType{type=TypeName{name='Greeting'}}, inputValueDefinitions=[], directives=[]}': [MethodFieldResolver{method=public java.lang.Iterable de.eno.prototyp.graphql.QueryResolver.getGreetingsAll()}, MethodFieldResolver{method=public java.lang.Iterable de.eno.prototyp.graphql.QueryResolver.getGreetingsAll()}]
    at com.coxautodev.graphql.tools.FieldResolverScanner.findFieldResolver(FieldResolverScanner.kt:39) ~[graphql-java-tools-5.2.0.jar:na]
    at com.coxautodev.graphql.tools.SchemaClassScanner.scanResolverInfoForPotentialMatches(SchemaClassScanner.kt:227) ~[graphql-java-tools-5.2.0.jar:na]
    at com.coxautodev.graphql.tools.SchemaClassScanner.handleRootType(SchemaClassScanner.kt:122) ~[graphql-java-tools-5.2.0.jar:na]
    at com.coxautodev.graphql.tools.SchemaClassScanner.scanForClasses(SchemaClassScanner.kt:80) ~[graphql-java-tools-5.2.0.jar:na]
    at com.coxautodev.graphql.tools.SchemaParserBuilder.scan(SchemaParserBuilder.kt:150) ~[graphql-java-tools-5.2.0.jar:na]
    at com.coxautodev.graphql.tools.SchemaParserBuilder.build(SchemaParserBuilder.kt:156) ~[graphql-java-tools-5.2.0.jar:na]
    at com.oembedler.moon.graphql.boot.GraphQLJavaToolsAutoConfiguration.schemaParser(GraphQLJavaToolsAutoConfiguration.java:65) ~[graphql-spring-boot-autoconfigure-4.3.0.jar:na]

So I don't know what happened but I think GraphQL can not map the Query type to the QueryResolver. But why?

When checking the internet, I read that Spring Boot Version is the reason. So I tested it with Spring Boot 1.5.8.RELEASE, without result.

However, when using an older GraphQL version (4.3.0 and 5.2.0) with Spring boot 1.5.8, it did seem to work fine. So then I tried to use these older GraphQL versions with the new Spring boot version, but then I get another exception:

2018-07-13 14:45:57.666  INFO 12872 --- [io-8080-exec-10] graphql.servlet.GraphQLServlet           : Bad POST request: parsing failed

java.lang.IllegalStateException: Unable to process parts as no multi-part configuration has been provided
    at org.apache.catalina.connector.Request.parseParts(Request.java:2826) ~[tomcat-embed-core-8.5.31.jar:8.5.31]
    at org.apache.catalina.connector.Request.getParts(Request.java:2793) ~[tomcat-embed-core-8.5.31.jar:8.5.31]
    at org.apache.catalina.connector.RequestFacade.getParts(RequestFacade.java:1084) ~[tomcat-embed-core-8.5.31.jar:8.5.31]
    at graphql.servlet.GraphQLServlet.lambda$new$2(GraphQLServlet.java:129) ~[graphql-java-servlet-5.1.0.jar:na]
    at graphql.servlet.GraphQLServlet.doRequest(GraphQLServlet.java:260) ~[graphql-java-servlet-5.1.0.jar:na]
    at graphql.servlet.GraphQLServlet.doPost(GraphQLServlet.java:278) ~[graphql-java-servlet-5.1.0.jar:na]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:661) ~[tomcat-embed-core-8.5.31.jar:8.5.31]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:742) ~[tomcat-embed-core-8.5.31.jar:8.5.31]

Now I'm really screwed up because of this incompatibility. I want to use the new versions of all libraries. Does anybody have a solution?

Ephebe answered 13/7, 2018 at 12:51 Comment(2)
I'm just here to shamelessly plug our own project: GraphQL SPQR Spring Boot Starter. it works with both Spring Boot 1 and 2, and is (in my honest but obviously biased opinion) infinitely easier to use. It's in very early stages of development, but is already quite stable and usable. See an example project here.Ingold
@Ingold I have seen your Framework allready. It seems to be the best way now to Use Spring Boot 2 and GraphQL. I will check it out.Ephebe
U
9

You're creating your resolver beans twice. Both your MutationResolver and QueryResolver are annotated with @Component, which means that Spring will create an instance and register it. However, within the GraphqlApplication you also created beans for both resolvers using the @Bean annotation.

This causes an issue, because the GraphQL library will look for a proper resolver for greetingsAll, but it will find two resolvers due to your bean being mapped twice.

The solution is to either remove the @Component annotation, or to remove the following bean configuration:

@Bean
public QueryResolver query() {
    return new QueryResolver();
}

@Bean
public MutationResolver mutation() {
    return new MutationResolver();
}
Ung answered 13/7, 2018 at 13:12 Comment(5)
that's right. I killed the @Bean-Methods but nevertheless there are some Exceptions like: graphql.servlet.GraphQLServlet : Bad POST request: parsing failed java.lang.IllegalStateException: Unable to process parts as no multi-part configuration has been providedEphebe
Since that question is no longer related to this problem, I suggest creating a new question. That keeps the Q&A over here clean.Ung
@JonasWolff did you ever end up creating another Question?Lowbrow
since it sounds like this Answer solved the [first] problem, I think this Answer should be AcceptedLowbrow
any idea why OP was able to get double Resolver Beans (without your Solution) to work? "older GraphQL version (4.3.0 and 5.2.0) with Spring boot 1.5.8"Lowbrow

© 2022 - 2024 — McMap. All rights reserved.