Why Cross-Language Code Plagiarism Detection Is Now Essential

The Problem That Traditional Plagiarism Checkers Miss

In Fall 2024, a mid-sized university with 450 CS enrollees ran into a familiar but rarely addressed problem. The introductory sequence taught Python in CS101 and Java in CS102. The final project for CS102 asked students to implement a simple social network graph. That same week, the CS101 instructor saw a nearly identical submission in Python — same data structures, same algorithm choices, same variable ordering. Manual inspection was trivial for this small case, but it raised an uncomfortable question: how many similar cross-language transfers were passing unnoticed?

Tools like MOSS and JPlag operate on token streams derived from language-specific lexers. They cannot compare a Python def with a Java public static void. Yet the underlying logic is often preserved verbatim. Students who understand the algorithm can trivially port it between languages, and instructors who teach multiple languages are blind to the problem.

“We had been focusing on within-language similarity for years. Cross-language copying was a blind spot that we simply assumed didn’t happen at scale.” — CS department chair, anonymous university survey, 2024

This case study describes a practical, reproducible workflow for cross-language code plagiarism detection that one department deployed midway through the semester. You can adapt the same steps using Codequiry’s code plagiarism checker and its cross-language analysis endpoint.

Step 1: Collect and Normalise Submissions

The first requirement is a central repository. In this case, the department exported all completed assignments from the LMS into a flat directory structure. Two languages were involved: Python (`.py`) and Java (`.java`). The total set was 320 files — 190 Java, 130 Python.

Normalisation is critical. Cross-language detection works on abstract syntax trees (ASTs) and token patterns, not on text. But you must first strip comments, normalise whitespace, and resolve imports. The team wrote a quick Python preprocessing step using pyparsing for Python and javaparser for Java:

# pseudocode for normalisation
import os
from pyparsing import remove_comment_lines
from javalang.parse import parse as parse_java

def normalise_py(filepath):
    with open(filepath) as f:
        code = f.read()
    # remove comments and docstrings, normalise whitespace
    clean = remove_comment_lines(code)
    clean = re.sub(r'\s+', ' ', clean).strip()
    return clean

def normalise_java(filepath):
    with open(filepath) as f:
        tree = parse_java(f.read())
    # strip comments, normalise spacing, remove annotations if desired
    # return flattened token stream
    return tokenise_ast(tree)

This step is optional — Codequiry’s API handles tokenisation internally — but running normalisation locally lets you inspect false positives before submission.

Step 2: Configure Cross-Language Detection Parameters

Codequiry’s cross-language mode compares files using AST-based fingerprints that are language-agnostic by design. The detector ignores syntax keywords and operator symbols and instead maps control-flow shapes, loop structures, and method-call sequences into a normalised integer array.

You can trigger cross-language analysis via the Codequiry Dashboard or the Codequiry Code Checking API. The key parameter is cross_lang=True. Here is an example API call using Python’s requests library:

import requests

API_KEY = "your_codequiry_api_key"
UPLOAD_URL = "https://api.codequiry.com/v1/check"

files = []
for dirpath, _, filenames in os.walk("submissions/"):
    for f in filenames:
        if f.endswith(('.py', '.java')):
            files.append(('files', (f, open(os.path.join(dirpath, f), 'rb'))))

payload = {
    'cross_lang': 'true',
    'min_similarity': 0.5,        # report pairs above 50%
    'check_online': 'false',       # disable web-source matching for speed
    'group_id': 'cs102-fall2024'  # optional metadata
}

resp = requests.post(UPLOAD_URL, files=files, data=payload, headers={"Authorization": f"Bearer {API_KEY}"})
result = resp.json()

Note on thresholds: Cross-language matches tend to produce raw similarity scores 10–15% lower than same-language pairs because the AST footprints are less dense. The department set min_similarity=0.4 to catch borderline cases and then manually verified the top 50 pairs.

Step 3: Interpret the Cross-Language Report

The API returns a list of pair objects with file_a, file_b, similarity, and a details URL that shows highlighted AST regions. In the initial run, the system flagged 44 pairs with similarity above 0.4. Twelve pairs had similarity above 0.7 — strong evidence of intentional copying.

Language Pair# PairsAvg Similarity
Python → Java260.58
Java → Python140.52
Python → Python40.45

One illustrative match: a Python student had submitted a BFS-based graph search identical in logic to a Java submission. The variable names were different (queueq, visited_setvis), but the while-loop structure, neighbor iteration order, and early exit condition matched perfectly. Codequiry’s AST comparison flagged this as a 0.81 similarity.

# Python submission (student A)
def shortest_path(graph, start, end):
    q = deque()
    q.append((start, 0))
    visited = {start}
    while q:
        node, dist = q.popleft()
        if node == end:
            return dist
        for neigh in graph[node]:
            if neigh not in visited:
                visited.add(neigh)
                q.append((neigh, dist+1))
    return -1

// Java submission (student B)
public int shortestPath(HashMap> graph, int start, int end) {
    Queue q = new LinkedList<>();
    Map dist = new HashMap<>();
    q.add(start);
    dist.put(start, 0);
    while (!q.isEmpty()) {
        int node = q.poll();
        if (node == end) return dist.get(node);
        for (int neigh : graph.get(node)) {
            if (!dist.containsKey(neigh)) {
                dist.put(neigh, dist.get(node) + 1);
                q.add(neigh);
            }
        }
    }
    return -1;
}

False positives occurred mostly with boilerplate — Java’s public static void main wrapper, or Python’s if __name__ == "__main__" pattern. The team excluded those by filtering out pairs where the matched regions were less than 15 AST nodes. This reduced the list from 44 to 27 manually reviewable cases.

Step 4: Feed Results Into the Grading Pipeline

The department already used a custom script to pull submissions from the LMS and populate a grading spreadsheet. They extended it to call the Codequiry API and write the similarity pairs into a MySQL table. TAs could then view flagged pairs directly from a web dashboard.

Here is the integration skeleton they implemented:

# grading_pipeline.py (excerpt)
import mysql.connector
from codequiry_api import cross_lang_check

def get_flagged_pairs(course_id):
    # 1. upload all assignments (Python + Java)
    result = cross_lang_check(course_id, cross_lang=True)
    # 2. store results
    db = mysql.connector.connect(...)
    cursor = db.cursor()
    for pair in result['pairs']:
        if pair['similarity'] > 0.4 and pair['matched_nodes'] > 15:
            cursor.execute("""
                INSERT INTO plagiarism_flags
                (course_id, submission_a, submission_b, similarity, detail_url)
                VALUES (%s, %s, %s, %s, %s)
            """, (course_id, pair['file_a'], pair['file_b'],
                  float(pair['similarity']), pair['detail_url']))
    db.commit()
    return len(result['pairs'])

After the batch run, the TAs received an automated email listing the top 10 pairs. The entire detection cycle — from normalisation to report generation — took under 3 minutes for 320 files.

Step 5: Conduct Follow-Up Interviews

Detection is only the first half. The department’s academic integrity committee scheduled 30-minute interviews for the seven students whose cross-language matches exceeded 0.7. During the interviews, students were shown the matched AST regions and asked to explain their design decisions.

What the committee found: five students admitted to sharing code with a friend who used a different language, reasoning that “it doesn’t count because it’s a different language.” Two claimed they both independently used the same textbook pseudocode — but the match included a unique off-by-one bug in both versions, which the committee regarded as decisive evidence.

The outcome: formal warnings for all seven, a mandatory academic integrity workshop, and a department-wide announcement clarifying that cross-language code reuse without attribution is plagiarism.

Lessons for Your Institution

Cross-language plagiarism is not rare — it is undetected. Any department offering multiple languages in introductory or intermediate courses should include it in their detection strategy. Based on this case study, here are the key takeaways:

  • Set a lower similarity threshold for cross-language comparisons (e.g., 0.4) to catch translated code. Manually review the top 50 pairs.
  • Exclude boilerplate regions by filtering on matched node count. Under 15 AST nodes is often noise.
  • Automate the workflow using the Codequiry API so detection runs silently after each deadline.
  • Educate students explicitly that translating code from one language to another is not a loophole.

The team plans to expand this workflow next semester to include C++ and JavaScript assignments, as well as cross-language matching against public web repositories (enabling Codequiry’s check_online parameter). With AI-generated code becoming more common, cross-language detection adds another layer — a student might ask ChatGPT for Java code but submit a manually translated Python version. The AST fingerprint will still catch the underlying logic.

Frequently Asked Questions

Does Codequiry support cross-language detection for more than two languages?

Yes. The current API supports Java, Python, C++, JavaScript, and C#. Mixed comparisons between any supported pair are available by setting cross_lang=True.

How sensitive is cross-language detection to variable renaming?

AST-based detection ignores identifier names entirely. Only control flow, data structure usage, and statement ordering are matched. Renaming a variable from adj_list to graph does not affect the score.

What is the false-positive rate for cross-language matches?

In the case study, after filtering by node count, the false-positive rate was about 12% based on manual review. Algorithmically simple tasks (e.g., a Fibonacci function) produce more false positives because every correct implementation looks similar across languages.

Cross-language plagiarism detection is no longer optional. As course portfolios grow more linguistically diverse, the tools we use must evolve. A simple API integration can uncover patterns that have been invisible for years.