Developer
News and Updates
Get Support
Sign in
Get Support
Sign in
DOCUMENTATION
Cloud
Data Center
Resources
Sign in
Sign in
DOCUMENTATION
Cloud
Data Center
Resources
Sign in
Last updated Sep 25, 2025

Phase 2: Analysis and preparation

Goal: Understand actual type usage to prepare for allowlist mode

Timeline: 2-4 weeks

Security improvement: Maintain current protection while gathering data

This phase focuses on analyzing your application's actual XStream usage patterns to prepare for the final migration to allowlist mode.

Step 2.1: Add usage monitoring

Create a wrapper to log type usage patterns:

1
2
public class TypeUsageMonitoringWrapper {
    private static final Logger logger = LoggerFactory.getLogger(TypeUsageMonitoringWrapper.class);
    private final XStream delegate;
    private final Set<String> observedTypes = ConcurrentHashMap.newKeySet();
    private final AtomicLong totalOperations = new AtomicLong(0);
    
    public TypeUsageMonitoringWrapper(XStream delegate) {
        this.delegate = delegate;
    }
    
    public Object fromXML(String xml) {
        totalOperations.incrementAndGet();
        
        try {
            Object result = delegate.fromXML(xml);
            logTypeUsage(result);
            return result;
        } catch (Exception e) {
            logger.warn("XStream processing failed", e);
            throw e;
        }
    }
    
    public String toXML(Object obj) {
        totalOperations.incrementAndGet();
        
        if (obj != null) {
            logTypeUsage(obj);
        }
        
        return delegate.toXML(obj);
    }
    
    private void logTypeUsage(Object obj) {
        if (obj != null) {
            String typeName = obj.getClass().getName();
            if (observedTypes.add(typeName)) {
                logger.info("XStream type observed: {} (total operations: {})", 
                    typeName, totalOperations.get());
            }
            
            // Also track nested types
            analyzeNestedTypes(obj);
        }
    }
    
    private void analyzeNestedTypes(Object obj) {
        // Analyze collections, maps, and other complex structures
        if (obj instanceof Collection) {
            ((Collection<?>) obj).forEach(this::logTypeUsage);
        } else if (obj instanceof Map) {
            Map<?, ?> map = (Map<?, ?>) obj;
            map.keySet().forEach(this::logTypeUsage);
            map.values().forEach(this::logTypeUsage);
        }
    }
    
    public Set<String> getObservedTypes() {
        return new HashSet<>(observedTypes);
    }
    
    public long getTotalOperations() {
        return totalOperations.get();
    }
}

Step 2.2: Deploy monitoring in production

Spring Boot configuration

1
2
@Configuration
@ConditionalOnProperty("migration.phase2.monitoring.enabled")
public class Phase2MonitoringConfig {
    
    @Bean
    @Primary
    public XStream monitoringXStream(@Qualifier("coreXStream") XStream coreXStream) {
        return new TypeUsageMonitoringWrapper(coreXStream);
    }
    
    @Bean("coreXStream")
    public XStream coreXStream() {
        XStream xstream = new BlocklistRestrictedXStream();
        xstream.addPermission(AnyTypePermission.ANY); // Still in blocklist mode
        // Apply existing configuration
        return xstream;
    }
}

Manual wrapper deployment

1
2
// Wrap existing XStream instances
XStream coreXStream = new BlocklistRestrictedXStream();
coreXStream.addPermission(AnyTypePermission.ANY);

TypeUsageMonitoringWrapper monitoringWrapper = 
    new TypeUsageMonitoringWrapper(coreXStream);

// Use wrapper instead of direct XStream
Object result = monitoringWrapper.fromXML(xmlData);
String xml = monitoringWrapper.toXML(dataObject);

Step 2.3: Collect and analyze data

Data collection service

1
2
@Component
@ConditionalOnProperty("migration.phase2.monitoring.enabled")
public class TypeUsageCollectionService {
    
    private final TypeUsageMonitoringWrapper monitoringWrapper;
    private final ObjectMapper objectMapper;
    
    @Scheduled(fixedRate = 300000) // Every 5 minutes
    public void collectUsageData() {
        UsageSnapshot snapshot = UsageSnapshot.builder()
            .timestamp(Instant.now())
            .observedTypes(monitoringWrapper.getObservedTypes())
            .totalOperations(monitoringWrapper.getTotalOperations())
            .build();
        
        // Store for analysis
        storeSnapshot(snapshot);
        
        // Log summary
        logger.info("Phase 2 monitoring - Types observed: {}, Operations: {}", 
            snapshot.getObservedTypes().size(), 
            snapshot.getTotalOperations());
    }
    
    private void storeSnapshot(UsageSnapshot snapshot) {
        try {
            String json = objectMapper.writeValueAsString(snapshot);
            Files.write(
                Paths.get("monitoring/usage-" + snapshot.getTimestamp() + ".json"),
                json.getBytes(),
                StandardOpenOption.CREATE
            );
        } catch (Exception e) {
            logger.error("Failed to store usage snapshot", e);
        }
    }
}

Analysis tools

1
2
public class UsageAnalysisTools {
    
    public TypeAnalysisReport analyzeCollectedData(List<UsageSnapshot> snapshots) {
        Set<String> allObservedTypes = snapshots.stream()
            .flatMap(snapshot -> snapshot.getObservedTypes().stream())
            .collect(Collectors.toSet());
        
        // Categorize types
        Set<String> applicationTypes = allObservedTypes.stream()
            .filter(type -> type.startsWith("com.yourcompany"))
            .collect(Collectors.toSet());
            
        Set<String> frameworkTypes = allObservedTypes.stream()
            .filter(type -> type.startsWith("java.") || 
                           type.startsWith("org.") ||
                           type.startsWith("javax."))
            .collect(Collectors.toSet());
        
        Set<String> thirdPartyTypes = allObservedTypes.stream()
            .filter(type -> !applicationTypes.contains(type) && 
                           !frameworkTypes.contains(type))
            .collect(Collectors.toSet());
        
        return TypeAnalysisReport.builder()
            .totalUniqueTypes(allObservedTypes.size())
            .applicationTypes(applicationTypes)
            .frameworkTypes(frameworkTypes)
            .thirdPartyTypes(thirdPartyTypes)
            .recommendations(generateRecommendations(applicationTypes, frameworkTypes))
            .build();
    }
    
    private List<String> generateRecommendations(Set<String> appTypes, Set<String> frameworkTypes) {
        List<String> recommendations = new ArrayList<>();
        
        if (appTypes.size() < 20) {
            recommendations.add("LOW_COMPLEXITY: Allowlist migration should be straightforward");
        } else if (appTypes.size() < 50) {
            recommendations.add("MEDIUM_COMPLEXITY: Plan careful allowlist migration");
        } else {
            recommendations.add("HIGH_COMPLEXITY: Consider gradual allowlist migration");
        }
        
        if (frameworkTypes.size() > 100) {
            recommendations.add("MANY_FRAMEWORK_TYPES: Consider using hierarchy allowlisting");
        }
        
        return recommendations;
    }
}

Step 2.4: Export and review data

Data export utility

1
2
@RestController
@ConditionalOnProperty("migration.phase2.monitoring.enabled")
public class MigrationDataController {
    
    private final TypeUsageMonitoringWrapper monitoringWrapper;
    private final UsageAnalysisTools analysisTools;
    
    @GetMapping("/migration/phase2/observed-types")
    public ResponseEntity<Set<String>> getObservedTypes() {
        return ResponseEntity.ok(monitoringWrapper.getObservedTypes());
    }
    
    @GetMapping("/migration/phase2/analysis-report")
    public ResponseEntity<TypeAnalysisReport> getAnalysisReport() {
        List<UsageSnapshot> snapshots = loadStoredSnapshots();
        TypeAnalysisReport report = analysisTools.analyzeCollectedData(snapshots);
        return ResponseEntity.ok(report);
    }
    
    @GetMapping("/migration/phase2/allowlist-config")
    public ResponseEntity<AllowlistConfig> generateAllowlistConfig() {
        Set<String> observedTypes = monitoringWrapper.getObservedTypes();
        AllowlistConfig config = createAllowlistConfig(observedTypes);
        return ResponseEntity.ok(config);
    }
}

Manual data analysis

After 1-2 weeks of monitoring, analyze the data:

1
2
// Export observed types
Set<String> types = monitoringWrapper.getObservedTypes();
types.forEach(System.out::println);

// Categorize types
Set<String> applicationTypes = types.stream()
    .filter(type -> type.startsWith("com.yourcompany"))
    .collect(Collectors.toSet());
    
Set<String> javaTypes = types.stream()
    .filter(type -> type.startsWith("java."))
    .collect(Collectors.toSet());

System.out.println("Application types: " + applicationTypes.size());
System.out.println("Java standard types: " + javaTypes.size());

Step 2.5: Create allowlist plan

Generate allowlist configuration

1
2
public class AllowlistConfigGenerator {
    
    public AllowlistConfiguration generateConfig(Set<String> observedTypes) {
        AllowlistConfiguration config = new AllowlistConfiguration();
        
        // Essential application types
        Set<Class<?>> coreTypes = observedTypes.stream()
            .filter(type -> type.startsWith("com.yourcompany.core"))
            .map(this::safeLoadClass)
            .filter(Objects::nonNull)
            .collect(Collectors.toSet());
        
        // Framework types that should use hierarchies
        Set<Class<?>> hierarchyTypes = identifyHierarchyTypes(observedTypes);
        
        // Individual types that need explicit allowlisting
        Set<Class<?>> explicitTypes = observedTypes.stream()
            .map(this::safeLoadClass)
            .filter(Objects::nonNull)
            .filter(clazz -> !shouldUseHierarchy(clazz))
            .collect(Collectors.toSet());
        
        config.setCoreTypes(coreTypes);
        config.setHierarchyTypes(hierarchyTypes);
        config.setExplicitTypes(explicitTypes);
        
        return config;
    }
    
    private Class<?> safeLoadClass(String className) {
        try {
            return Class.forName(className);
        } catch (ClassNotFoundException e) {
            logger.warn("Could not load class for allowlist: {}", className);
            return null;
        }
    }
    
    private boolean shouldUseHierarchy(Class<?> clazz) {
        return Collection.class.isAssignableFrom(clazz) ||
               Map.class.isAssignableFrom(clazz) ||
               Number.class.isAssignableFrom(clazz);
    }
}

Document the migration plan

1
2
/**
 * Phase 3 Migration Plan
 * Generated from Phase 2 analysis
 * 
 * Analysis Period: 2024-12-01 to 2024-12-15
 * Total Operations: 45,678
 * Unique Types: 127
 * 
 * Type Breakdown:
 * - Application types: 23
 * - Framework types: 89
 * - Third-party types: 15
 * 
 * Migration Complexity: MEDIUM
 * Estimated Phase 3 Duration: 2-3 weeks
 */
public class Phase3MigrationPlan {
    
    // Essential application types (must allowlist)
    private static final Class<?>[] CORE_TYPES = {
        UserProfile.class,
        ApplicationConfig.class,
        SessionData.class,
        // ... identified from analysis
    };
    
    // Framework hierarchies (safe to allowlist broadly)
    private static final Class<?>[] HIERARCHY_TYPES = {
        Collection.class,
        Map.class,
        Number.class,
        // ... identified from analysis
    };
    
    // Individual framework types (explicit allowlist)
    private static final Class<?>[] FRAMEWORK_TYPES = {
        ArrayList.class,
        HashMap.class,
        Date.class,
        // ... identified from analysis
    };
}

Step 2.6: Risk assessment

Identify potential issues

1
2
public class MigrationRiskAssessment {
    
    public RiskAssessment assessMigrationRisk(Set<String> observedTypes) {
        RiskAssessment assessment = new RiskAssessment();
        
        // Check for problematic patterns
        long dynamicTypes = observedTypes.stream()
            .filter(type -> type.contains("$Proxy"))
            .count();
        
        long unknownThirdParty = observedTypes.stream()
            .filter(type -> !type.startsWith("java.") && 
                           !type.startsWith("com.yourcompany"))
            .count();
        
        // Assess risk levels
        if (dynamicTypes > 10) {
            assessment.addRisk("HIGH", "Many dynamic proxy types detected");
        }
        
        if (unknownThirdParty > 20) {
            assessment.addRisk("MEDIUM", "Many third-party types in use");
        }
        
        if (observedTypes.size() > 100) {
            assessment.addRisk("MEDIUM", "Large number of types may complicate allowlist");
        }
        
        return assessment;
    }
}

Step 2.7: Prepare Phase 3 environment

Create test configuration

1
2
@TestConfiguration
public class Phase3TestConfig {
    
    @Bean
    @Primary
    public XStream testAllowlistXStream() {
        XStream xstream = new BlocklistRestrictedXStream();
        // Note: NO AnyTypePermission.ANY - this is allowlist mode
        
        // Configure based on Phase 2 analysis
        configureFromAnalysis(xstream);
        
        return xstream;
    }
    
    private void configureFromAnalysis(XStream xstream) {
        // Load configuration generated from Phase 2 data
        AllowlistConfiguration config = loadPhase2Configuration();
        
        xstream.allowTypes(config.getCoreTypes().toArray(new Class[0]));
        xstream.allowTypes(config.getFrameworkTypes().toArray(new Class[0]));
        
        for (Class<?> hierarchyType : config.getHierarchyTypes()) {
            xstream.allowTypeHierarchy(hierarchyType);
        }
    }
}

Success criteria for Phase 2

Phase 2 is complete when:

  1. Monitoring has been running for at least 2 weeks in production
  2. Type usage data has been collected and analyzed
  3. Allowlist configuration has been generated from real usage data
  4. Risk assessment has been completed
  5. Phase 3 test environment has been prepared
  6. Migration plan is documented and approved

Next steps

After completing Phase 2:

  1. Review analysis results with the development team
  2. Address any high-risk findings before proceeding
  3. Begin Phase 3 migration to allowlist mode
  4. Keep monitoring running during Phase 3 for validation

Phase 2 provides the crucial data needed to make Phase 3 migration smooth and successful.

Rate this page: