Jackson Conflicting setter definitions for property in @Endpoint

During spring boot initialization, more precisely during OpenAPI generation of @Endpoint classes, I am having a Jackson exception due to a conflict in setter definitions for a property.

java.lang.IllegalArgumentException: Conflicting setter definitions for property “fines”: domain.Vehicle#setFines(java.util.List) vs domain.Vehicle#setFines(java.util.Map)

As I cannot modify the class Vehicle because it is being imported from a .jar extracted of an old system I tried to extend it in a new class VehicleDAO in order to use the annotation @JsonIgnore in those setFines() methods, however the annotation is being ignored.

@Endpoint
public class VehicleController {
...
   public VehicleDAO getVehicle(String id) {
        Vehicle bean = repository.getVehicle(id);
        return  VehicleDAO.of(bean));
   }
}
public class VehicleDAO extends Vehicle {

   @JsonIgnore
   public void setFines(Map<String, String> map) {
      ...
   }

   @JsonIgnore
   public void setFines(List<String> list) {
      ...
   }

}

I am using vaadin and vaadin-spring-boot-starter v24.4.6 with jackson-databind v2.15.4. I have tried several days to fix the issue but I have not had any progress so I would appreciate any help to fix the issue.

Thank you,
Ed

Could you try @JsonIgnoreProperties({"fines"}) on your DAO?

Hi, thanks for your answer. I tried that and It did not work, debugging jackson I saw a comment that suggested ** If I understood fine*** that @JsonIgnoreProperties and @JsonIgnore were used for serialization during endpoint communication but not during the introspection phase or something like that, which I understand is what hilla is doing during the openAPI generation for the angular code generation. Take my comments careful here, they are assumptions as it is easy to be lost or understand something wrong debugging libraries.

We support the @JsonIgnoreProperties as stated in the docs. Unfortunately, as you pointed out, it is applied after Jackson has determined the whole set of properties, so fines would have been excluded when it’s too late.

I tried some workarounds and it seems to work by adding @JsonProperty("fines") to both the getter and one of the setters, the one you want to use.

Let me know if this works for you.

Hi Luciano,

On one side, and unfortunately, the @JsonProperty(“fines”) did not work. On the other, I could manage to solve the issue, but with a few hours of debugging and testing in order to discover how everything was working on the vaadin-maven-plugin.

I am sharing the solution in case anyone has this issue:

                <groupId>com.vaadin</groupId>
                <artifactId>vaadin-maven-plugin</artifactId>
                <version>${vaadin.version}</version>
                <configuration>
				    <parser>
				        <plugins>
							<disableAllDefaults>true</disableAllDefaults>
				        	<use>
				                <plugin>
				                    <name>com.example.core.hilla.MyBackbonePlugin</name>
				                    <configuration implementation="com.vaadin.hilla.parser.plugins.backbone.BackbonePluginConfiguration">
				                    	<objectMapperFactoryClassName>com.example.core.hilla.MyJacksonObjectMapperFactory</objectMapperFactoryClassName>
				                    </configuration>
				                </plugin>
				                <plugin>
				                    <name>com.example.core.hilla.MyTransferTypesPlugin</name>
								</plugin>
				                <plugin>
				                    <name>com.example.core.hilla.MyNonnullPlugin</name>
								</plugin>
				                <plugin>
				                    <name>com.example.core.hilla.MySubTypesPlugin</name>
								</plugin>
				                <plugin>
				                    <name>com.example.core.hilla.MyModelPlugin</name>
								</plugin>					
							</use>
				        </plugins>
				    </parser>
			    </configuration>
public class MyJacksonObjectMapperFactory implements JacksonObjectMapperFactory {
    @Override
    public ObjectMapper build() {
    ..
    mapper.addMixIn(Vehicle.class, VehicleMixIn.class);
    ..
    }
}
public abstract class VehicleMixIn {
    @JsonIgnore
    abstract void setFines(List<String> list)

    @JsonIgnore
    abstract void setFines(Map<String, String> map)
}

public class MyBackbonePlugin extends AbstractCompositePlugin<BackbonePluginConfiguration> {
    
    public MyBackbonePlugin() {
        super(new EndpointPlugin(), 
              new EndpointExposedPlugin(), 
              new MethodPlugin(), 
              new MethodParameterPlugin(), 
              new EntityPlugin(), 
              new PropertyPlugin(), 
              new TypeSignaturePlugin());
    }
    
}
public class MyTransferTypesPlugin extends AbstractPlugin<PluginConfiguration> {
    private final TransferTypesPlugin real;

    public MyTransferTypesPlugin() {
        super();
        real = new TransferTypesPlugin();
    }
    
    @Override
    public Collection<Class<? extends Plugin>> getRequiredPlugins() {
        return List.of(MyBackbonePlugin.class);
    }
    
    @Override
    public void enter(NodePath<?> nodePath) {
        real.enter(nodePath);
    }

    @Override
    public void exit(NodePath<?> nodePath) {
        real.exit(nodePath);
    }

    @Override
    public NodeDependencies scan(NodeDependencies nodeDependencies) {
        return real.scan(nodeDependencies);
    }
    
}
public class MyNonnullPlugin extends AbstractPlugin<NonnullPluginConfig> {
    private final NonnullPlugin real;

    public MyNonnullPlugin() {
        super();
        real = new NonnullPlugin();
    }
    
    @Override
    public Collection<Class<? extends Plugin>> getRequiredPlugins() {
        return List.of(MyBackbonePlugin.class);
    }
    
    @Override
    public void enter(NodePath<?> nodePath) {
        real.enter(nodePath);
    }

    @Override
    public void exit(NodePath<?> nodePath) {
        real.exit(nodePath);
    }

    @Override
    public NodeDependencies scan(NodeDependencies nodeDependencies) {
        return real.scan(nodeDependencies);
    }
    
}
public class MySubTypesPlugin extends AbstractPlugin<PluginConfiguration> {
    private final SubTypesPlugin real;

    public MySubTypesPlugin() {
        super();
        real = new SubTypesPlugin();
    }
    
    @Override
    public Collection<Class<? extends Plugin>> getRequiredPlugins() {
        return List.of(MyBackbonePlugin.class);
    }
    
    @Override
    public void enter(NodePath<?> nodePath) {
        real.enter(nodePath);
    }

    @Override
    public void exit(NodePath<?> nodePath) {
        real.exit(nodePath);
    }

    @Override
    public NodeDependencies scan(NodeDependencies nodeDependencies) {
        return real.scan(nodeDependencies);
    }
}
public class MyModelPlugin extends AbstractPlugin<PluginConfiguration> {
    private final ModelPlugin real;
    
    public MyModelPlugin() {
        super();
        real = new ModelPlugin();
    }
    
    @Override
    public Collection<Class<? extends Plugin>> getRequiredPlugins() {
        return List.of(MyBackbonePlugin.class);
    }
    
    @Override
    public void enter(NodePath<?> nodePath) {
        real.enter(nodePath);
    }

    @Override
    public void exit(NodePath<?> nodePath) {
        real.exit(nodePath);
    }

    @Override
    public NodeDependencies scan(NodeDependencies nodeDependencies) {
        return real.scan(nodeDependencies);
    }
    
}

The only drawback is that I could not avoid these amount of plugins inheritances, as If I tried to configure a BackbonePluginConfiguration with a custom JacksonObjectMapperFactory for the BackbonePlugin, which is passed to the PropertyPlugin, I need to disable the BackbonePlugin which ends up in a situation of dependencies that I could not avoid.

So far everything its working.

Thanks again for your support and answering my messages,
Best Regards,
Eduardo

Hi,

I would really appreciate if you could create a minimal example that reproduces the issue, so that I can try to implement a fix.

I tried to reproduce it as such:

    public static class Vehicle {
        public String name;
        private List<String> fines = List.of("Speeding", "Parking", "Red light");
        public List<String> getFines() {
            return fines;
        }
        public void setFines(Map<String, String> map) {
            fines = map.values().stream().toList();
        }

        public void setFines(List<String> list) {
            fines = list;
        }
    }

    public static class VehicleDTO {
        private final Vehicle vehicle;

        public VehicleDTO(Vehicle vehicle) {
            this.vehicle = vehicle;
        }

        @JsonProperty("fines")
        public List<String> getFines() {
            return vehicle.getFines();
        }

        @JsonProperty("fines")
        public void setFines(List<String> fines) {
            vehicle.setFines(fines);
        }

        public void setFines(Map<String, String> map) {
            vehicle.setFines(map);
        }
    }

    public VehicleDTO vehicle() {
        return new VehicleDTO(new Vehicle());
    }

but this works, so there must be some difference.