For DevelopersFebruary 05, 2025

How to Convert Snake Case to Camel Case in JSON with Java

Easily convert JSON keys from snake_case to camelCase in Java with detailed steps and code examples.

As you work with enterprise applications, you'll often face the challenge of handling data format conversions between different systems. While JSON (JavaScript Object Notation) has become the de facto standard for data interchange, naming conventions often differ between systems - databases typically use snake_case while Java and JavaScript applications favour camelCase

In this guide, we'll explore enterprise-grade solutions that you can implement for converting JSON key naming conventions. We'll provide a step-by-step breakdown, detailed code examples, and best practices to make the process seamless and error-free.

🚀 Join Index.dev to work on innovative remote projects, grow your Java expertise, and build a thriving career with top global companies!

 

Concept Explanation

Snake Case vs. Camel Case

  • Snake Case: Words are separated by underscores (_), e.g., user_name.
  • Camel Case: Words are joined, and subsequent words start with an uppercase letter, e.g., userName.

Relevance of Conversion

Conversion is often required to align with naming conventions in Java or JavaScript-based applications. In modern applications, APIs often use snake_case for compatibility, while Java objects prefer camelCase for standardization. Proper conversion ensures compatibility between external JSON sources and Java applications. JSON files usually adhere to specific structural guidelines, making tools like JSON Schema invaluable for validation and understanding key conventions.

Read More: How to Deserialize JSON in Java Using a Mapper

 

Method 1: Custom Recursive Method for Conversion

Step 1: Parse the JSON Input

First, let's set up our core converter class and implement the initial JSON parsing.

import org.json.JSONObject;
import org.json.JSONArray;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Objects;

public class SnakeToCamelCaseConverter {
    // Thread-safe cache for frequently converted keys
    private static final ConcurrentHashMap<String, String> keyCache = new ConcurrentHashMap<>();
    
    public static void main(String[] args) {
        String jsonString = "{ \"first_name\": \"John\", \"last_name\": \"Doe\", " +
                           "\"contact_info\": { \"email_address\": \"[email protected]\", " +
                           "\"phone_number\": \"123-456-7890\" } }";
        
        JSONObject jsonObject = new JSONObject(jsonString);
        JSONObject converted = convertKeysToCamelCase(jsonObject);
        System.out.println(converted.toString(4)); // Pretty-print with indent
    }
    
    public static JSONObject convertKeysToCamelCase(JSONObject jsonObject) {
        Objects.requireNonNull(jsonObject, "JSON object cannot be null");
        
        long startTime = System.nanoTime();
        try {
            return processJsonObject(jsonObject);
        } finally {
            MetricsCollector.recordConversionTime(System.nanoTime() - startTime);
        }
    }
}

Step 2: Implement the Key Conversion Utility

Now, let's create an efficient utility method for converting individual keys.

public static String toCamelCase(String snakeCaseKey) {
    if (snakeCaseKey == null || snakeCaseKey.isEmpty()) {
        return snakeCaseKey;
    }

    // Use key cache for improved performance
    return keyCache.computeIfAbsent(snakeCaseKey, key -> {
        StringBuilder camelCase = new StringBuilder(key.length());
        boolean nextUpper = false;

        for (int i = 0; i < key.length(); i++) {
            char ch = key.charAt(i);
            if (ch == '_') {
                nextUpper = true;
            } else {
                camelCase.append(nextUpper ? 
                    Character.toUpperCase(ch) : 
                    i == 0 ? Character.toLowerCase(ch) : ch);
                nextUpper = false;
            }
        }
        
        return camelCase.toString();
    });
}

Step 3: Handle Nested Structures

Let's implement recursive processing for nested JSON objects and arrays

private static JSONObject processJsonObject(JSONObject jsonObject) {
    JSONObject result = new JSONObject();
    
    for (String key : jsonObject.keySet()) {
        Object value = jsonObject.get(key);
        String camelCaseKey = toCamelCase(key);
        
        if (value instanceof JSONObject) {
            value = processJsonObject((JSONObject) value);
        } else if (value instanceof JSONArray) {
            value = processArray((JSONArray) value);
        }
        
        result.put(camelCaseKey, value);
    }
    return result;
}

private static JSONArray processArray(JSONArray array) {
    JSONArray newArray = new JSONArray();
    for (int i = 0; i < array.length(); i++) {
        Object item = array.get(i);
        if (item instanceof JSONObject) {
            newArray.put(processJsonObject((JSONObject) item));
        } else if (item instanceof JSONArray) {
            newArray.put(processArray((JSONArray) item));
        } else {
            newArray.put(item);
        }
    }
    return newArray;
}

// Helper class for metrics collection
private static class MetricsCollector {
    private static void recordConversionTime(long nanos) {
        // Implement your metrics collection logic here
        // Example: log to monitoring system, update metrics, etc.
    }
}

Step 4: Testing and Debugging

Let's test our implementation with both flat and nested JSON structures to ensure proper conversion.

public class ConverterTest {
    public static void main(String[] args) {
        // Test case 1: Nested JSON
        String nestedJson = "{\n" +
            "  \"first_name\": \"John\",\n" +
            "  \"last_name\": \"Doe\",\n" +
            "  \"contact_info\": {\n" +
            "    \"email_address\": \"[email protected]\",\n" +
            "    \"phone_number\": \"123-456-7890\"\n" +
            "  }\n" +
            "}";

        // Test case 2: Flat JSON with array
        String flatJson = "{\n" +
            "  \"user_id\": 123,\n" +
            "  \"favorite_colors\": [\"sky_blue\", \"forest_green\"],\n" +
            "  \"is_active\": true\n" +
            "}";

        // Test both cases
        testConversion("Nested JSON Test", nestedJson);
        testConversion("Flat JSON Test", flatJson);
    }

    private static void testConversion(String testName, String input) {
        System.out.println("\nRunning: " + testName);
        System.out.println("Input:");
        System.out.println(input);
        
        JSONObject converted = SnakeToCamelCaseConverter.convertKeysToCamelCase(
            new JSONObject(input)
        );
        
        System.out.println("\nOutput:");
        System.out.println(converted.toString(2));
    }
}

Example Output:

// Nested JSON Test Output:
{
  "firstName": "John",
  "lastName": "Doe",
  "contactInfo": {
    "emailAddress": "[email protected]",
    "phoneNumber": "123-456-7890"
  }
}

// Flat JSON Test Output:
{
  "userId": 123,
  "favoriteColors": ["skyBlue", "forestGreen"],
  "isActive": true
}

 

Method 2: Using Jackson Library for Automatic Conversion

When you're working with large-scale applications, you'll want to consider using established libraries like Jackson for better maintainability and integration with your existing ecosystem. Jackson library is a powerful tool for JSON serialization and deserialization, converting snake_case to camelCase automatedly with minimal effort. Jackson provides a PropertyNamingStrategy that handles the mapping seamlessly. 

Let's leverage Jackson's powerful customization features to create a robust, configurable solution that you can easily integrate with your enterprise frameworks. 

Step 1: Identify Input Structures

Let's look at the JSON structures we'll be handling:

// Simple user data
{
  "first_name": "John",
  "last_name": "Doe",
  "user_age": 30
}

// Complex nested structure
{
  "user_details": {
    "personal_info": {
      "first_name": "John",
      "last_name": "Doe",
      "birth_date": "1990-01-01"
    },
    "contact_details": {
      "email_address": "[email protected]",
      "phone_numbers": [
        {"home_number": "123-456-7890"},
        {"work_number": "098-765-4321"}
      ]
    }
  }
}

Step 2: Configure ObjectMapper

First, let's set up Jackson with enterprise-ready configuration.

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import java.util.Objects;

public class JacksonConverter {
    private static final ObjectMapper objectMapper = createObjectMapper();
    
    private static ObjectMapper createObjectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        
        // Configure naming strategy and features
        mapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
        mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
        mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
        
        // Register modules for handling Java 8 date/time types
        mapper.registerModule(new JavaTimeModule());
        
        return mapper;
    }
}

Step 3: Create Model Classes

Let's define our model classes for type-safe conversion.

import java.time.LocalDate;
import java.util.List;

public class UserDetails {
    private PersonalInfo personalInfo;
    private ContactDetails contactDetails;
    
    // Getters and setters
    public PersonalInfo getPersonalInfo() { return personalInfo; }
    public void setPersonalInfo(PersonalInfo personalInfo) { 
        this.personalInfo = personalInfo; 
    }
    public ContactDetails getContactDetails() { return contactDetails; }
    public void setContactDetails(ContactDetails contactDetails) { 
        this.contactDetails = contactDetails; 
    }
}

public class PersonalInfo {
    private String firstName;
    private String lastName;
    private LocalDate birthDate;
    
    // Getters and setters
    public String getFirstName() { return firstName; }
    public void setFirstName(String firstName) { this.firstName = firstName; }
    public String getLastName() { return lastName; }
    public void setLastName(String lastName) { this.lastName = lastName; }
    public LocalDate getBirthDate() { return birthDate; }
    public void setBirthDate(LocalDate birthDate) { this.birthDate = birthDate; }
}

public class ContactDetails {
    private String emailAddress;
    private List<PhoneNumber> phoneNumbers;
    
    // Getters and setters
    public String getEmailAddress() { return emailAddress; }
    public void setEmailAddress(String emailAddress) { 
        this.emailAddress = emailAddress; 
    }
    public List<PhoneNumber> getPhoneNumbers() { return phoneNumbers; }
    public void setPhoneNumbers(List<PhoneNumber> phoneNumbers) { 
        this.phoneNumbers = phoneNumbers; 
    }
}

public class PhoneNumber {
    private String homeNumber;
    private String workNumber;
    
    // Getters and setters
    public String getHomeNumber() { return homeNumber; }
    public void setHomeNumber(String homeNumber) { this.homeNumber = homeNumber; }
    public String getWorkNumber() { return workNumber; }
    public void setWorkNumber(String workNumber) { this.workNumber = workNumber; }
}

Step 4: Implement Conversion Methods

Now, let's create type-safe conversion methods with proper error handling.

public class JacksonConverter {
    // ... previous ObjectMapper configuration ...

    public static <T> T convertFromJson(String json, Class<T> targetClass) {
        Objects.requireNonNull(json, "JSON string cannot be null");
        Objects.requireNonNull(targetClass, "Target class cannot be null");
        
        try {
            return objectMapper.readValue(json, targetClass);
        } catch (Exception e) {
            throw new JsonConversionException(
                "Failed to convert JSON to " + targetClass.getSimpleName(), e);
        }
    }

    public static String convertToJson(Object object) {
        Objects.requireNonNull(object, "Source object cannot be null");
        
        try {
            return objectMapper.writeValueAsString(object);
        } catch (Exception e) {
            throw new JsonConversionException(
                "Failed to serialize object of type " + 
                object.getClass().getSimpleName(), e);
        }
    }

    public static String convertToPrettyJson(Object object) {
        Objects.requireNonNull(object, "Source object cannot be null");
        
        try {
            return objectMapper.writerWithDefaultPrettyPrinter()
                             .writeValueAsString(object);
        } catch (Exception e) {
            throw new JsonConversionException(
                "Failed to serialize object of type " + 
                object.getClass().getSimpleName(), e);
        }
    }
}

class JsonConversionException extends RuntimeException {
    public JsonConversionException(String message, Throwable cause) {
        super(message, cause);
    }
}

Step 5: Testing the Implementation

Let's test our Jackson implementation with different scenarios.

public class JacksonConverterTest {
    public static void main(String[] args) {
        // Test complex nested structure
        String complexJson = """
            {
              "user_details": {
                "personal_info": {
                  "first_name": "John",
                  "last_name": "Doe",
                  "birth_date": "1990-01-01"
                },
                "contact_details": {
                  "email_address": "[email protected]",
                  "phone_numbers": [
                    {"home_number": "123-456-7890"},
                    {"work_number": "098-765-4321"}
                  ]
                }
              }
            }""";

        try {
            // Convert JSON to object
            UserDetails userDetails = JacksonConverter.convertFromJson(
                complexJson, UserDetails.class);
            
            // Convert back to JSON (snake_case)
            String convertedJson = JacksonConverter.convertToPrettyJson(userDetails);
            
            System.out.println("Converted to Object and back to JSON:");
            System.out.println(convertedJson);
            
        } catch (JsonConversionException e) {
            System.err.println("Conversion failed: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

Example Output:

{
  "user_details" : {
    "personal_info" : {
      "first_name" : "John",
      "last_name" : "Doe",
      "birth_date" : "1990-01-01"
    },
    "contact_details" : {
      "email_address" : "[email protected]",
      "phone_numbers" : [ {
        "home_number" : "123-456-7890"
      }, {
        "work_number" : "098-765-4321"
      } ]
    }
  }
}

Why Use This Approach?

The Jackson-based solution offers you comprehensive JSON handling capabilities right out of the box. It automatically manages snake_case to camelCase conversion for all nested structures while providing type-safe operations and robust error handling. You'll get built-in support for Java 8+ date/time types, collections, and custom data types, along with configurable output formatting including pretty printing. 

As Jackson is a standard in the Java ecosystem, it integrates seamlessly with Spring and Jakarta EE frameworks, making it a production-ready choice for enterprise applications. The library's extensive documentation and community support ensure you can quickly resolve any implementation challenges you encounter.

For more information on Jackson's features and capabilities, you can refer to the Jackson GitHub documentation.

For Java developers seeking alternatives to the Jackson library for JSON processing, Gson and JSON.simple are notable options. Gson is well-suited for serializing and deserializing Java objects to JSON and vice versa, offering a straightforward API. JSON.simple provides a lightweight and efficient approach, particularly advantageous for handling large JSON files. Performance benchmarks suggest that while Jackson excels with larger files, Gson performs efficiently with smaller datasets.

For a comprehensive comparison between Jackson and Gson, you can refer to Baeldung's article.

Additionally, this GitHub repository offers a curated list of JSON libraries and resources that might be of interest.

 

Best Practices

  1. Ensure Idempotency: Conversions should not unintentionally modify values or nested structures.
  2. Optimize Performance: For large JSON data, consider streaming parsers like Jackson to reduce memory usage.
  3. Validate Input: Check for invalid JSON or unexpected formats before conversion.
  4. Handle Arrays: Always account for arrays containing objects or primitive types.

 

Performance Considerations

As you implement these solutions in your enterprise environment, here's what you should consider:

  1. Choose Method 1 (Custom Converter) when you:
    • Need to process large volumes of JSON data
    • Want fine-grained control over the conversion process
    • Have critical performance requirements
    • Need to optimize memory usage
  2. Choose Method 2 (Jackson-based) when you're:
    • Working with existing Spring/Jakarta EE applications
    • Looking for seamless integration with other JSON processing
    • Prioritizing maintainability over raw performance
    • Requiring type safety

If you're new to Java or looking to brush up on foundational concepts like classes and methods, check out the official Java tutorials for step-by-step guidance.

 

Use Cases

  • API Integration: Align database snake_case keys with camelCase frontend conventions.
  • Codebase Unification: Standardize naming conventions across microservices.
  • Data Transformation: Convert logs or exported data for analytics platforms.

Explore More: How to Identify and Optimize Long-Running Queries in Java

 

Conclusion

As we've seen, converting between snake_case and camelCase in your enterprise Java applications requires careful consideration of various factors but is a vital step in ensuring seamless data handling and code consistency. By leveraging Java’s powerful capabilities, you can optimize this process efficiently. You can choose a custom converter for optimal performance in high-throughput scenarios, or implement our Jackson-based solution for better integration with enterprise frameworks and improved maintainability.

For Developers:

Unlock top remote Java opportunities! Join Index.dev and get matched with global companies for high-paying, long-term projects.

For Companies:

Hire the best Java developers through Index.dev—access elite, vetted talent in just 48 hours with a 30-day free trial.

Share

Pallavi PremkumarPallavi PremkumarTechnical Content Writer

Related Articles

For EmployersHow Specialized AI Is Transforming Traditional Industries
Artificial Intelligence
Artificial intelligence is changing how traditional industries work. Companies are no longer relying only on general skills. Instead, they are using AI tools and specialized experts to improve productivity, reduce costs, and make better decisions.
Ali MojaharAli MojaharSEO Specialist
For EmployersHow to Scale an Engineering Team After Series A Funding
Tech HiringInsights
Most Series A founders hire too fast, in the wrong order, and regret it by month six. Here's the hiring sequence that actually works, and the mistakes worth avoiding before they cost you a Series B.
Mihai GolovatencoMihai GolovatencoTalent Director