# JSON Deserialization Error Resolution
This document outlines the permanent solution for JSON deserialization errors in the Jakarta Migration MCP project, specifically addressing issues with Jackson deserialization of domain model classes.
## Problem Context
The error "Cannot construct instance of `adrianmikula.jakartamigration.advancedscanning.domain.TransitiveDependencyUsage` (no Creators, like default constructor, exist)" occurs when:
1. Jackson attempts to deserialize JSON into Java objects
2. The target class lacks a no-arg constructor
3. The class contains final fields that need initialization
4. Deserialization fails because Jackson cannot instantiate the class
This is a common issue with Lombok-annotated classes and domain models that use final fields.
## Root Cause Analysis
### Why the Error Occurs
```java
// The problematic class structure
@Getter
public class TransitiveDependencyUsage {
private final String artifactId; // Final field
private final String groupId; // Final field
// ... other final fields
// No no-arg constructor exists
// Jackson cannot instantiate this class
}
```
### Why Jackson Needs a No-Arg Constructor
1. Jackson creates an instance first using the no-arg constructor
2. Then sets field values via reflection or setters
3. Without a no-arg constructor, instantiation fails
## Permanent Solution
### Step 1: Add Lombok Annotations
```java
@Getter
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class TransitiveDependencyUsage {
private final String artifactId;
private final String groupId;
private final String version;
private final String javaxPackage;
private final String severity;
private final String recommendation;
}
```
### Step 2: Understanding the Annotations
#### `@Getter`
- Generates getter methods for all fields
- Required for Jackson to access field values
#### `@AllArgsConstructor`
- Generates a constructor with all fields as parameters
- Used for normal object instantiation
- Matches the constructor calls in the codebase
#### `@NoArgsConstructor(access = AccessLevel.PRIVATE)`
- Generates a private no-arg constructor
- Required for Jackson deserialization
- Private access prevents accidental instantiation
### Step 3: Why This Works
#### Jackson's Deserialization Process
1. **Instantiation**: Jackson calls the no-arg constructor (private access is sufficient)
2. **Field Assignment**: Jackson uses reflection to set field values
3. **Final Fields**: Since fields are final, they must be initialized in the constructor
#### The Magic of Lombok
- `@NoArgsConstructor` generates: `private TransitiveDependencyUsage() {}`
- This satisfies Jackson's requirement
- The private access level prevents misuse while allowing Jackson access
## Alternative Solutions
### Option 1: Manual Constructor Implementation
```java
@Getter
public class TransitiveDependencyUsage {
private final String artifactId;
private final String groupId;
private final String version;
private final String javaxPackage;
private final String severity;
private final String recommendation;
// No-arg constructor for Jackson
private TransitiveDependencyUsage() {
this.artifactId = null;
this.groupId = null;
this.version = null;
this.javaxPackage = null;
this.severity = null;
this.recommendation = null;
}
// All-args constructor for normal usage
public TransitiveDependencyUsage(String artifactId, String groupId, String version,
String javaxPackage, String severity, String recommendation) {
this.artifactId = artifactId;
this.groupId = groupId;
this.version = version;
this.javaxPackage = javaxPackage;
this.severity = severity;
this.recommendation = recommendation;
}
}
```
### Option 2: Jackson Annotations
```java
@Getter
@JsonCreator
public class TransitiveDependencyUsage {
@JsonProperty("artifactId")
private final String artifactId;
@JsonProperty("groupId")
private final String groupId;
// ... other fields
@JsonCreator
public TransitiveDependencyUsage(@JsonProperty("artifactId") String artifactId,
@JsonProperty("groupId") String groupId,
// ... other parameters
) {
this.artifactId = artifactId;
this.groupId = groupId;
// ... other initializations
}
}
```
## Implementation Guidelines
### When to Use Lombok Solution
- **Preferred approach** for most cases
- **Clean and concise** code
- **Consistent** with existing codebase
- **Maintains** Lombok benefits
### When to Use Manual Solution
- **Complex validation** required
- **Custom logic** in constructors
- **Framework requirements** for specific annotations
- **Legacy code** compatibility
### When to Use Jackson Annotations
- **Fine-grained control** needed
- **Specific field mapping** required
- **Integration** with existing Jackson configuration
## Verification Checklist
### Before Implementation
- [ ] Identify all classes with deserialization errors
- [ ] Check for existing Lombok usage
- [ ] Verify final field requirements
- [ ] Review Jackson configuration
### After Implementation
- [ ] Test deserialization with sample JSON
- [ ] Verify normal object instantiation
- [ ] Check for compilation errors
- [ ] Validate with unit tests
### Long-term Maintenance
- [ ] Document the pattern in standards
- [ ] Update code review guidelines
- [ ] Add tests for deserialization
- [ ] Monitor for similar issues
## Common Pitfalls
### 1. Missing Lombok Dependency
```groovy
// build.gradle.kts
implementation "org.projectlombok:lombok:${lombokVersion}"
kapt "org.projectlombok:lombok:${lombokVersion}"
```
### 2. IDE Lombok Plugin Not Installed
- Install Lombok plugin in IDE
- Enable annotation processing
- Rebuild project after changes
### 3. Build Tool Configuration
```groovy
// build.gradle.kts
plugins {
id 'java'
id 'org.jetbrains.kotlin.jvm' version '1.9.10'
}
compileJava {
options.annotationProcessorPath = configurations.annotationProcessor
}
```
## Testing the Fix
### Unit Test Example
```java
@Test
void testDeserialization() throws Exception {
String json = "{\"artifactId\":\"test\",\"groupId\":\"com.test\"}";
ObjectMapper mapper = new ObjectMapper();
TransitiveDependencyUsage result = mapper.readValue(json, TransitiveDependencyUsage.class);
assertNotNull(result);
assertEquals("test", result.getArtifactId());
assertEquals("com.test", result.getGroupId());
}
```
### Integration Test
```java
@Test
void testAdvancedScanningDeserialization() throws Exception {
// Test the full deserialization flow
AdvancedScanningService service = new AdvancedScanningService();
AdvancedScanSummary summary = service.loadInitialState();
assertNotNull(summary);
assertNotNull(summary.getTransitiveDependencyResult());
}
```
## Performance Considerations
### Constructor Overhead
- No-arg constructor is lightweight
- Final field initialization is efficient
- Jackson reflection overhead is minimal
### Memory Usage
- Objects are created once during deserialization
- Final fields prevent modification
- Memory footprint is optimal
## Security Considerations
### Input Validation
- Jackson automatically handles null values
- Final fields prevent post-construction modification
- Consider adding validation if business rules require
### Deserialization Security
- Use Jackson's deserialization features
- Consider enabling security modules
- Validate input data if coming from untrusted sources
## Migration Strategy
### For Existing Code
1. Identify all classes with deserialization errors
2. Apply Lombok solution to each class
3. Test compilation and functionality
4. Update documentation
### For New Code
1. Use Lombok annotations by default
2. Add no-arg constructor for deserialization
3. Document the pattern
4. Include in code reviews
## Related Patterns
### Data Transfer Objects (DTOs)
- Use similar patterns for DTOs
- Consider using `@Data` for simple DTOs
- Add validation annotations if needed
### Entity Classes
- Use `@Entity` with Lombok for JPA entities
- Add `@NoArgsConstructor` for JPA requirements
- Consider `@Builder` for complex entities
### Configuration Classes
- Use `@ConfigurationProperties` with Lombok
- Add validation annotations
- Consider using `@ConstructorBinding`
## References
1. [Lombok Documentation](https://projectlombok.org/features/all)
2. [Jackson Databind Documentation](https://github.com/FasterXML/jackson-databind)
3. [Effective Java - Item 2: Consider a builder when faced with many constructor parameters](https://www.oreilly.com/library/view/effective-java-3rd/9780134686097/)
4. [Java Constructor Best Practices](https://www.baeldung.com/java-constructor-best-practices)
---
**Last Updated:** 2026-02-27
**Version:** 1.0
**Author:** Jakarta Migration Team
**Reviewers:** Adrian Mikula, Kilo AI Assistant